summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.md17
-rw-r--r--Cargo.toml2
-rw-r--r--source/benoit/benoit.rs22
-rw-r--r--source/benoit/benoit/app.rs27
-rw-r--r--source/benoit/benoit/app/animate.rs203
-rw-r--r--source/benoit/benoit/app/dump.rs8
-rw-r--r--source/benoit/benoit/app/handle_keys.rs146
-rw-r--r--source/benoit/benoit/app/initialise.rs24
-rw-r--r--source/benoit/benoit/app/interactive.rs122
-rw-r--r--source/benoit/benoit/app/poll_events.rs15
-rw-r--r--source/benoit/benoit/app/run.rs5
-rw-r--r--source/benoit/benoit/app/still.rs55
-rw-r--r--source/benoit/benoit/complex.rs (renamed from source/benoit/benoit/app/image_filename.rs)28
-rw-r--r--source/benoit/benoit/configuration.rs190
-rw-r--r--source/benoit/benoit/configuration/load.rs199
-rw-r--r--source/benoit/benoit/fractal.rs95
-rw-r--r--source/benoit/benoit/palette.rs6
-rw-r--r--source/benoit/benoit/render.rs12
-rw-r--r--source/benoit/benoit/render/colour.rs74
-rw-r--r--source/benoit/benoit/render/colour_data.rs28
-rw-r--r--source/benoit/benoit/render/iterate/burning_ship.rs24
-rw-r--r--source/benoit/benoit/render/iterate/mandelbrot.rs20
-rw-r--r--source/benoit/benoit/render/iterate/multibrot3.rs30
-rw-r--r--source/benoit/benoit/render/iterate/tricorn.rs20
-rw-r--r--source/benoit/benoit/render/render_data.rs35
-rw-r--r--source/benoit/benoit/render/render_point/julia.rs38
-rw-r--r--source/benoit/benoit/render/render_point/normal.rs48
-rw-r--r--source/benoit/benoit/renderer.rs (renamed from source/benoit/benoit/rendering.rs)13
-rw-r--r--source/benoit/benoit/renderer/colour.rs86
-rw-r--r--source/benoit/benoit/renderer/render.rs (renamed from source/benoit/benoit/render/render.rs)49
-rw-r--r--source/benoit/benoit/video/draw_textual_feedback.rs7
-rw-r--r--source/benoit/benoit/video/draw_translation_feedback.rs19
-rw-r--r--source/benoit/benoit/video/initialise.rs1
33 files changed, 957 insertions, 711 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5c9a622..30a4fc3 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,20 @@
+# 2.3.0
+
+* Bump minor version
+* Re-enable window border
+* Support translation when rendering Julias
+* Rework key handling
+* Update naming convention
+* Update controls guide
+* Add key for resetting viewport
+* Support perturbation
+* Refactor and modulise code structure
+* Rework animations
+* Update configuration
+* Rework row colourisers as point colourisers
+* Fix image file extensions
+* Use our own complex type
+
# 2.2.0
* Bump minor version
diff --git a/Cargo.toml b/Cargo.toml
index a6768dd..a0dce81 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "benoit"
-version = "2.2.0"
+version = "2.3.0"
authors = ["Gabriel Bjørnager Jensen"]
edition = "2021"
description = "Mandelbrot renderer."
diff --git a/source/benoit/benoit.rs b/source/benoit/benoit.rs
index a366c61..715a311 100644
--- a/source/benoit/benoit.rs
+++ b/source/benoit/benoit.rs
@@ -21,22 +21,19 @@
If not, see <https://www.gnu.org/licenses/>.
*/
-extern crate rug;
-
-use rug::Float;
-
pub mod app;
+pub mod complex;
pub mod configuration;
pub mod fractal;
pub mod palette;
-pub mod rendering;
+pub mod renderer;
pub mod render;
pub mod video;
pub const VERSION: [u32; 0x3] = [
- 0x2,
- 0x2,
- 0x0,
+ 0x2, // Major
+ 0x3, // Minor
+ 0x0, // Patch
];
pub const PRECISION: u32 = 0x80;
@@ -49,15 +46,6 @@ pub enum ImageFormat {
Webp,
}
-pub struct FeedbackInfo<'a> {
- prev_centre_real: &'a Float,
- prev_centre_imag: &'a Float,
- prev_zoom: &'a Float,
- next_centre_real: &'a Float,
- next_centre_imag: &'a Float,
- next_zoom: &'a Float,
-}
-
pub fn width_height_ratio(width: u32, height: u32) -> (f32, f32) {
return if width > height {
(1.0, height as f32 / width as f32)
diff --git a/source/benoit/benoit/app.rs b/source/benoit/benoit/app.rs
index fa047f9..a281304 100644
--- a/source/benoit/benoit/app.rs
+++ b/source/benoit/benoit/app.rs
@@ -22,10 +22,10 @@
*/
use crate::benoit::ImageFormat;
+use crate::benoit::complex::Complex;
use crate::benoit::fractal::Fractal;
use crate::benoit::palette::Palette;
-use crate::benoit::rendering::Rendering;
-use crate::benoit::render::{IteratorFunction, PointRenderer};
+use crate::benoit::renderer::Renderer;
use crate::benoit::video::Video;
extern crate rug;
@@ -37,47 +37,38 @@ pub mod animate;
pub mod drop;
pub mod dump;
pub mod handle_keys;
-pub mod image_filename;
pub mod initialise;
pub mod interactive;
pub mod poll_events;
pub mod run;
-pub mod still;
pub struct App {
- #[allow(dead_code)]
thread_count: u32,
fractal: Fractal,
- rendering: Rendering,
+ renderer: Renderer,
canvas_width: u32,
canvas_height: u32,
scale: u32,
- frame_count: u32,
+ frame_start: u32,
+ frame_stop: u32,
- centre_real: Float,
- centre_imag: Float,
- zoom: Float,
+ centre: Complex,
+ extra: Complex,
+ zoom: Float,
max_iter_count: u32,
- inverse: bool,
-
- multibrot_exponent: f32,
-
palette: Palette,
colour_range: f32,
dump_path: String,
image_format: ImageFormat,
- video: Option<Video>,
-
interactive: bool,
do_render: bool,
do_textual_feedback: bool,
- point_renderer: PointRenderer,
- iterator_function: IteratorFunction,
+ video: Option<Video>,
}
diff --git a/source/benoit/benoit/app/animate.rs b/source/benoit/benoit/app/animate.rs
index 9a6b4ba..157c213 100644
--- a/source/benoit/benoit/app/animate.rs
+++ b/source/benoit/benoit/app/animate.rs
@@ -21,9 +21,12 @@
If not, see <https://www.gnu.org/licenses/>.
*/
-use crate::benoit::PRECISION;
+use crate::benoit::{ImageFormat, PRECISION};
use crate::benoit::app::App;
-use crate::benoit::render::{colour, render};
+use crate::benoit::complex::Complex;
+use crate::benoit::fractal::Fractal;
+use crate::benoit::palette::Palette;
+use crate::benoit::renderer::Renderer;
extern crate rug;
extern crate sdl2;
@@ -34,7 +37,9 @@ use std::time::Instant;
impl App {
pub fn animate(&self) -> i32 {
- assert!(self.frame_count > 0x1);
+ let frame_count = self.frame_stop - self.frame_start;
+
+ if frame_count == 0x0 { return self.still() };
let (mut iter_count_buffer, mut square_dist_buffer, mut image) = App::allocate_buffers(self.canvas_width, self.canvas_height);
@@ -43,61 +48,159 @@ impl App {
let zoom_stop = Float::with_val(PRECISION, &self.zoom);
- let zoom_factor = {
- // To get the zoom factor, we first want the 'a'
- // value of the growth function from (0) to
- // (frame_count) on the x-dimension and from
- // (zoom_start) to (zoom_stop) on the y-dimension:
- //
- // a = nroot(x1-x0,y1/y0),
- //
- // but this may be simplified for use with Rug
- // because
- //
- // nroot(a,b) = b^(1/a),
- //
- // making the final equation
- //
- // (y1/y0)^(1/(x1-x0)) = (zoom_stop/zoom_start)^(1/(frame_count-1)).
- //
- // N.b. that we subtract one from frame_count as we
- // want the final render to have exactly the
- // specified zoom.
-
- let exponent = Float::with_val(PRECISION, 1.0 / (self.frame_count as f32 - 1.0));
-
- let mut factor = Float::with_val(PRECISION, &zoom_stop / &zoom);
- factor.pow_assign(exponent);
-
- factor
- };
-
- eprintln!("animating {} frames at {}{:+}i to {:.3} (fac. {:.3})", self.frame_count, self.centre_real.to_f32(), self.centre_imag.to_f32(), zoom_stop.to_f32(), zoom_factor.to_f32());
-
- for frame in 0x0..self.frame_count {
- eprint!("{frame:010} (2^{:.9}x)...", zoom.to_f64().log2());
-
- let time_start = Instant::now();
+ let zoom_factor = get_zoom_factor(&zoom, &zoom_stop, self.frame_stop);
- render(&mut iter_count_buffer[..], &mut square_dist_buffer[..], self.canvas_width, self.canvas_height, &self.centre_real, &self.centre_imag, &zoom, self.max_iter_count, self.inverse, self.point_renderer, self.iterator_function);
+ zoom = if self.frame_start > 0x0 {
+ let mut zoom = zoom_factor.clone();
+ zoom.pow_assign(frame_count);
- let render_time = time_start.elapsed();
- eprint!(" {:.3}ms, colouring...", render_time.as_micros() as f32 / 1000.0);
-
- colour(&mut image[..], self.canvas_width, self.canvas_height, self.multibrot_exponent, self.max_iter_count, self.colour_range, self.palette, &iter_count_buffer[..], &square_dist_buffer[..]);
+ zoom
+ } else {
+ zoom
+ };
- let colour_time = time_start.elapsed() - render_time;
- eprint!(" {:.3}ms...", colour_time.as_micros() as f32 / 1000.0);
+ eprintln!("animating from #{} to #{} ({} frame(s)) at {}{:+}i to {:.3}x (fac. ~{:.3})", self.frame_start, self.frame_stop, frame_count + 0x1, self.centre.real.to_f32(), self.centre.imag.to_f32(), zoom_stop.to_f32(), zoom_factor.to_f32());
+
+ for frame in 0x0..=frame_count {
+ let frame_name = format!("frame{frame:010}");
+
+ dump_frame(
+ self.dump_path.as_str(),
+ frame_name.as_str(),
+ &mut image[..],
+ &mut iter_count_buffer[..],
+ &mut square_dist_buffer[..],
+ self.renderer,
+ &self.fractal,
+ self.palette,
+ self.canvas_width,
+ self.canvas_height,
+ &self.centre,
+ &self.extra,
+ &zoom,
+ self.max_iter_count,
+ self.colour_range,
+ self.image_format,
+ );
- let path = App::image_filename(format!("{}/frame{frame:010}.webp", self.dump_path).as_str(), self.image_format);
+ zoom *= &zoom_factor;
+ }
- self.dump(path.as_str(), &image, self.canvas_width, self.canvas_height);
+ return 0x0;
+ }
- eprintln!(" done");
+ fn still(&self) -> i32 {
+ let (mut iter_count_buffer, mut square_dist_buffer, mut image) = App::allocate_buffers(self.canvas_width, self.canvas_height);
- zoom *= &zoom_factor;
- }
+ const FRAME_NAME: &str = "render";
+
+ dump_frame(
+ self.dump_path.as_str(),
+ FRAME_NAME,
+ &mut image[..],
+ &mut iter_count_buffer[..],
+ &mut square_dist_buffer[..],
+ self.renderer,
+ &self.fractal,
+ self.palette,
+ self.canvas_width,
+ self.canvas_height,
+ &self.centre,
+ &self.extra,
+ &self.zoom,
+ self.max_iter_count,
+ self.colour_range,
+ self.image_format,
+ );
return 0x0;
}
}
+
+fn dump_frame(
+ dump_path: &str,
+ name: &str,
+ image: &mut [u8],
+ iter_count_buffer: &mut [u32],
+ square_dist_buffer: &mut [f32],
+ renderer: Renderer,
+ fractal: &Fractal,
+ palette: Palette,
+ canvas_width: u32,
+ canvas_height: u32,
+ centre: &Complex,
+ extra: &Complex,
+ zoom: &Float,
+ max_iter_count: u32,
+ colour_range: f32,
+ image_format: ImageFormat,
+) {
+ eprint!("\"{name}\" (2^{:.9}x)...", zoom.to_f64().log2());
+
+ let time_start = Instant::now();
+
+ renderer.render(
+ iter_count_buffer,
+ square_dist_buffer,
+ fractal,
+ canvas_width,
+ canvas_height,
+ centre,
+ zoom,
+ extra,
+ max_iter_count,
+ );
+
+ let render_time = time_start.elapsed();
+ eprint!(" {:.3}ms, colouring...", render_time.as_micros() as f32 / 1000.0);
+
+ renderer.colour(
+ image,
+ palette,
+ canvas_width,
+ canvas_height,
+ fractal.exponent(),
+ max_iter_count,
+ colour_range,
+ iter_count_buffer,
+ square_dist_buffer,
+ );
+
+ let colour_time = time_start.elapsed() - render_time;
+ eprint!(" {:.3}ms...", colour_time.as_micros() as f32 / 1000.0);
+
+ let path = format!("{dump_path}/{name}");
+
+ App::dump(path.as_str(), &image, canvas_width, canvas_height, image_format);
+ eprintln!(" done");
+}
+
+fn get_zoom_factor(zoom_start: &Float, zoom_stop: &Float, frame_count: u32) -> Float {
+ assert!(frame_count > 0x0);
+
+ // To get the zoom factor, we first want the 'a'
+ // value of the growth function from (0) to
+ // (frame_count) on the x-dimension and from
+ // (zoom_start) to (zoom_stop) on the y-dimension:
+ //
+ // a = nroot(x1-x0,y1/y0),
+ //
+ // but this may be simplified for use with Rug
+ // because
+ //
+ // nroot(a,b) = b^(1/a),
+ //
+ // making the final equation
+ //
+ // (y1/y0)^(1/(x1-x0)) = (zoom_stop/zoom_start)^(1/(frame_count-1)).
+
+ let frame_start: f32 = 0.0;
+ let frame_stop: f32 = frame_count as f32;
+
+ let exponent = Float::with_val(PRECISION, 1.0 / (frame_stop - frame_start));
+
+ let mut factor = Float::with_val(PRECISION, zoom_stop / zoom_start);
+ factor.pow_assign(exponent);
+
+ factor
+}
diff --git a/source/benoit/benoit/app/dump.rs b/source/benoit/benoit/app/dump.rs
index a0fb3c5..fe92558 100644
--- a/source/benoit/benoit/app/dump.rs
+++ b/source/benoit/benoit/app/dump.rs
@@ -31,8 +31,8 @@ use std::fs::{File, write};
use std::io::BufWriter;
impl App {
- pub fn dump(&self, path: &str, image: &[u8], canvas_width: u32, canvas_height: u32) {
- match self.image_format {
+ pub fn dump(path: &str, image: &[u8], canvas_width: u32, canvas_height: u32, image_format: ImageFormat) {
+ match image_format {
ImageFormat::Png => dump_png( path, image, canvas_width, canvas_height),
ImageFormat::Webp => dump_webp(path, image, canvas_width, canvas_height),
}
@@ -40,6 +40,8 @@ impl App {
}
fn dump_png(path: &str, image: &[u8], canvas_width: u32, canvas_height: u32) {
+ let path = path.to_owned() + ".png";
+
let file = File::create(path).expect("unable to create file");
let file_buffer = BufWriter::new(file);
@@ -54,6 +56,8 @@ fn dump_png(path: &str, image: &[u8], canvas_width: u32, canvas_height: u32) {
}
fn dump_webp(path: &str, image: &[u8], canvas_width: u32, canvas_height: u32) {
+ let path = path.to_owned() + ".webp";
+
let encoder = webp::Encoder::from_rgb(&image[..], canvas_width, canvas_height);
let data = encoder.encode_lossless();
diff --git a/source/benoit/benoit/app/handle_keys.rs b/source/benoit/benoit/app/handle_keys.rs
index 3f9c0dd..73d47f7 100644
--- a/source/benoit/benoit/app/handle_keys.rs
+++ b/source/benoit/benoit/app/handle_keys.rs
@@ -21,39 +21,41 @@
If not, see <https://www.gnu.org/licenses/>.
*/
-use crate::benoit::{PRECISION};
+use crate::benoit::PRECISION;
use crate::benoit::app::App;
-use crate::benoit::fractal::Fractal;
-use crate::benoit::palette::Palette;
-use crate::benoit::render::{IteratorFunction, PointRenderer};
-use crate::benoit::rendering::Rendering;
+use crate::benoit::configuration::Configuration;
+use crate::benoit::renderer::Renderer;
extern crate rug;
extern crate sdl2;
-use rug::Float;
-use sdl2::keyboard::Scancode;
+use rug::{Assign, Float};
+use sdl2::keyboard::{KeyboardState, Scancode};
pub const MIN_COLOUR_RANGE: f32 = 2.0;
impl App {
#[must_use]
- pub fn handle_keys(&mut self, scan_code: Scancode) -> bool {
+ pub fn handle_keys(&mut self, scan_code: Scancode, state: KeyboardState) -> bool {
+ if scan_code == Scancode::C { self.do_render = true };
+
+ if state.is_scancode_pressed(Scancode::LShift) { return self.handle_shift_keys(scan_code) };
+
match scan_code {
- Scancode::C => self.do_render = true,
Scancode::Escape => return true,
Scancode::F1 => self.do_textual_feedback = !self.do_textual_feedback,
- Scancode::LAlt => (self.fractal, self.multibrot_exponent, self.iterator_function) = cycle_fractal(self.fractal, -0x1),
- Scancode::LCtrl => self.inverse = toggle_inverse(self.inverse),
- Scancode::Left => self.palette = cycle_palette(self.palette, -0x1),
- Scancode::RAlt => (self.fractal, self.multibrot_exponent, self.iterator_function) = cycle_fractal(self.fractal, 0x1),
- Scancode::Right => self.palette = cycle_palette(self.palette, 0x1),
- Scancode::Tab => (self.rendering, self.point_renderer) = toggle_julia(self.rendering),
- Scancode::Z => eprintln!("c = {}{:+}i, {}x @ {} iter. (range.: {:.3})", &self.centre_real, &self.centre_imag, &self.zoom, self.max_iter_count, self.colour_range),
+ Scancode::LAlt => self.cycle_fractal(-0x1),
+ Scancode::LCtrl => self.toggle_inverse(),
+ Scancode::Left => self.cycle_palette(-0x1),
+ Scancode::RAlt => self.cycle_fractal(0x1),
+ Scancode::Right => self.cycle_palette(0x1),
+ Scancode::Tab => self.cycle_rendering(),
+ Scancode::X => self.reset_viewport(),
+ Scancode::Z => self.dump_info(),
_ => {},
- }
+ };
- self.handle_translation(scan_code);
+ self.translate(scan_code);
self.max_iter_count = match scan_code {
Scancode::F => self.max_iter_count * 0x2,
@@ -72,7 +74,26 @@ impl App {
return false;
}
- fn handle_translation(&mut self, scan_code: Scancode) {
+ #[must_use]
+ fn handle_shift_keys(&mut self, scan_code: Scancode) -> bool {
+ let translate_ammount = Float::with_val(PRECISION, 4.0 / 64.0 / &self.zoom);
+
+ match scan_code {
+ Scancode::A => self.extra.real -= &translate_ammount,
+ Scancode::D => self.extra.real += &translate_ammount,
+ _ => {},
+ };
+
+ match scan_code {
+ Scancode::S => self.extra.imag -= &translate_ammount,
+ Scancode::W => self.extra.imag += &translate_ammount,
+ _ => {},
+ };
+
+ return false;
+ }
+
+ fn translate(&mut self, scan_code: Scancode) {
const ZOOM_FACTOR: f32 = 1.0 + 1.0 / 4.0;
match scan_code {
@@ -81,67 +102,74 @@ impl App {
_ => {},
};
- let translate_ammount = {
- let mut ammount = Float::with_val(PRECISION, 4.0);
- ammount /= 16.0;
- ammount /= &self.zoom;
-
- ammount
- };
+ let translate_ammount = Float::with_val(PRECISION, 4.0 / 16.0 / &self.zoom);
match scan_code {
- Scancode::A => self.centre_real -= &translate_ammount,
- Scancode::D => self.centre_real += &translate_ammount,
+ Scancode::A => self.centre.real -= &translate_ammount,
+ Scancode::D => self.centre.real += &translate_ammount,
_ => {},
};
match scan_code {
- Scancode::S => self.centre_imag -= &translate_ammount,
- Scancode::W => self.centre_imag += &translate_ammount,
+ Scancode::S => self.centre.imag -= &translate_ammount,
+ Scancode::W => self.centre.imag += &translate_ammount,
_ => {},
};
}
-}
-fn cycle_fractal(fractal: Fractal, distance: i8) -> (Fractal, f32, IteratorFunction) {
- let fractal = fractal + distance;
- let exponent = fractal.get_exponent();
+ fn cycle_fractal(&mut self, distance: i8) {
+ self.fractal.cycle(distance);
+
+ eprintln!("renderer the {}", self.fractal.kind().name());
+ }
- let iterator_function = fractal.get_iterator();
+ fn cycle_rendering(&mut self) {
+ let renderer = self.renderer.cycle();
- eprintln!("rendering the {}", fractal.get_name());
+ match renderer {
+ Renderer::Julia => eprintln!("enabled the julia set"),
+ Renderer::Normal => eprintln!("disabled the julia set"),
+ };
- return (fractal, exponent, iterator_function);
-}
+ self.renderer = renderer;
+ }
-fn toggle_julia(rendering: Rendering) -> (Rendering, PointRenderer) {
- let rendering = rendering.cycle();
+ fn toggle_inverse(&mut self) {
+ let inverse = !self.fractal.inverse();
- let point_renderer = rendering.get_point_renderer();
+ match inverse {
+ false => eprintln!("reverting fractal"),
+ true => eprintln!("inverting fractals"),
+ };
+
+ self.fractal.set_inverse(inverse);
+ }
- match rendering {
- Rendering::Julia => eprintln!("enabled the julia set"),
- Rendering::Normal => eprintln!("disabled the julia set"),
- };
+ fn cycle_palette(&mut self, direction: i8) {
+ let palette = self.palette.cycle(direction);
- return (rendering, point_renderer);
-}
+ eprintln!("using palette \"{}\"", palette.name());
-fn toggle_inverse(inverse: bool) -> bool {
- let inverse = !inverse;
+ self.palette = palette;
+ }
- match inverse {
- false => eprintln!("reverting fractal"),
- true => eprintln!("inverting fractals"),
- };
+ fn reset_viewport(&mut self) {
+ self.centre.real.assign(Configuration::DEFAULT_CENTRE_REAL);
+ self.centre.imag.assign(Configuration::DEFAULT_CENTRE_IMAG);
+ self.zoom.assign( Configuration::DEFAULT_ZOOM);
- return inverse;
-}
+ self.extra.real.assign(Configuration::DEFAULT_EXTRA_REAL);
+ self.extra.imag.assign(Configuration::DEFAULT_EXTRA_IMAG);
-fn cycle_palette(palette: Palette, direction: i8) -> Palette {
- let palette = palette.cycle(direction);
+ self.max_iter_count = Configuration::DEFAULT_MAX_ITER_COUNT;
- eprintln!("using palette \"{}\"", palette.get_name());
+ self.colour_range = Configuration::DEFAULT_COLOUR_RANGE;
+ }
- return palette;
+ fn dump_info(&self) {
+ eprintln!("info dump:");
+ eprintln!(" c = {}{:+}i ({}x)", &self.centre.real, &self.centre.imag, &self.zoom);
+ eprintln!(" w = {}{:+}i", &self.extra.real, &self.extra.imag);
+ eprintln!(" max. iter.: {}, col. range: {}", self.max_iter_count, self.colour_range);
+ }
}
diff --git a/source/benoit/benoit/app/initialise.rs b/source/benoit/benoit/app/initialise.rs
index f4b4f8e..2876e97 100644
--- a/source/benoit/benoit/app/initialise.rs
+++ b/source/benoit/benoit/app/initialise.rs
@@ -22,6 +22,7 @@
*/
use crate::benoit::app::App;
+use crate::benoit::complex::Complex;
use crate::benoit::configuration::Configuration;
use crate::benoit::video::Video;
@@ -62,23 +63,21 @@ impl App {
return App {
thread_count: thread_count,
- fractal: configuration.fractal,
- rendering: configuration.rendering,
+ fractal: configuration.fractal,
+ renderer: configuration.renderer,
canvas_width: configuration.canvas_width,
canvas_height: configuration.canvas_height,
scale: configuration.scale,
- frame_count: configuration.frame_count,
+ frame_start: configuration.frame_start,
+ frame_stop: configuration.frame_stop,
- centre_real: configuration.centre_real,
- centre_imag: configuration.centre_imag,
- zoom: configuration.zoom,
+ centre: Complex { real: configuration.centre_real, imag: configuration.centre_imag },
+ zoom: configuration.zoom,
- max_iter_count: configuration.max_iter_count,
-
- inverse: configuration.inverse,
+ extra: Complex { real: configuration.extra_real, imag: configuration.extra_imag },
- multibrot_exponent: configuration.fractal.get_exponent(),
+ max_iter_count: configuration.max_iter_count,
palette: configuration.palette,
colour_range: configuration.colour_range,
@@ -86,14 +85,11 @@ impl App {
dump_path: configuration.dump_path,
image_format: configuration.image_format,
- video: video,
-
interactive: configuration.interactive,
do_render: true,
do_textual_feedback: false,
- point_renderer: configuration.rendering.get_point_renderer(),
- iterator_function: configuration.fractal.get_iterator(),
+ video: video,
};
}
}
diff --git a/source/benoit/benoit/app/interactive.rs b/source/benoit/benoit/app/interactive.rs
index a5cc51f..94db797 100644
--- a/source/benoit/benoit/app/interactive.rs
+++ b/source/benoit/benoit/app/interactive.rs
@@ -21,10 +21,8 @@
If not, see <https://www.gnu.org/licenses/>.
*/
-use crate::benoit::FeedbackInfo;
use crate::benoit::app::App;
-use crate::benoit::render::{colour, render};
-use crate::benoit::rendering::Rendering;
+use crate::benoit::complex::Complex;
use crate::benoit::video::Video;
extern crate rug;
@@ -46,12 +44,11 @@ impl App {
// Used for colouring:
let mut prev_max_iter_count = self.max_iter_count;
- let mut prev_multibrot_exponent = self.multibrot_exponent;
+ let mut prev_multibrot_exponent = self.fractal.exponent();
// Used for translation feedback:
- let mut prev_centre_real = self.centre_real.clone();
- let mut prev_centre_imag = self.centre_imag.clone();
- let mut prev_zoom = self.zoom.clone();
+ let mut prev_centre = self.centre.clone();
+ let mut prev_zoom = self.zoom.clone();
loop {
let frame_start = Instant::now();
@@ -59,29 +56,31 @@ impl App {
if self.poll_events(&mut event_pump) { break }
if self.do_render {
- eprint!("rendering...");
-
- let time_start = Instant::now();
-
- render(&mut iter_count_buffer[..], &mut square_dist_buffer[..], self.canvas_width, self.canvas_height, &self.centre_real, &self.centre_imag, &self.zoom, self.max_iter_count, self.inverse, self.point_renderer, self.iterator_function);
- let render_time = time_start.elapsed();
-
- eprintln!(" {:.3}ms", render_time.as_micros() as f32 / 1000.0);
+ self.render(&mut iter_count_buffer[..], &mut square_dist_buffer[..]);
prev_max_iter_count = self.max_iter_count;
- prev_multibrot_exponent = self.multibrot_exponent;
+ prev_multibrot_exponent = self.fractal.exponent();
- prev_centre_real.assign(&self.centre_real);
- prev_centre_imag.assign(&self.centre_imag);
- prev_zoom.assign(&self.zoom);
+ prev_centre.assign(&self.centre);
+ prev_zoom.assign( &self.zoom);
self.do_render = false;
- }
+ };
- colour(&mut image[..], self.canvas_width, self.canvas_height, prev_multibrot_exponent, prev_max_iter_count.min(self.max_iter_count), self.colour_range, self.palette, &iter_count_buffer[..], &square_dist_buffer[..]);
+ self.renderer.colour(
+ &mut image[..],
+ self.palette,
+ self.canvas_width,
+ self.canvas_height,
+ prev_multibrot_exponent,
+ prev_max_iter_count.min(self.max_iter_count),
+ self.colour_range,
+ &iter_count_buffer[..],
+ &square_dist_buffer[..],
+ );
video.draw(&image[..], self.canvas_width, self.canvas_height, self.scale);
- self.draw_feedback(&mut video, &prev_centre_real, &prev_centre_imag, &prev_zoom);
+ self.draw_feedback(&mut video, &prev_centre, &prev_zoom);
video.update();
@@ -91,43 +90,50 @@ impl App {
return 0x0;
}
- pub fn draw_feedback(&self, video: &mut Video, prev_centre_real: &Float, prev_centre_imag: &Float, prev_zoom: &Float) {
- let julia = match self.rendering {
- Rendering::Julia => true,
- _ => false,
- };
+ pub fn render(&self, iter_count_buffer: &mut [u32], square_dist_buffer: &mut [f32]) {
+ eprint!("rendering...");
+
+ let time_start = Instant::now();
+
+ self.renderer.render(
+ iter_count_buffer,
+ square_dist_buffer,
+ &self.fractal,
+ self.canvas_width,
+ self.canvas_height,
+ &self.centre,
+ &self.zoom,
+ &self.extra,
+ self.max_iter_count,
+ );
+
+ let render_time = time_start.elapsed();
+ eprintln!(" {:.3}ms", render_time.as_micros() as f32 / 1000.0);
+ }
+
+ pub fn draw_feedback(&self, video: &mut Video, prev_centre: &Complex, prev_zoom: &Float) {
if {
// Don't draw translation feedback if rendering a
// Julia set or if we haven't done any viewport
// translations.
- !julia
- && (&self.centre_real != prev_centre_real
- || &self.centre_imag != prev_centre_imag
- || &self.zoom != prev_zoom)
+ &self.centre.real != &prev_centre.real
+ || &self.centre.imag != &prev_centre.imag
+ || &self.zoom != prev_zoom
}{
- let feedback_info = FeedbackInfo {
- prev_centre_real: prev_centre_real,
- prev_centre_imag: prev_centre_imag,
- prev_zoom: prev_zoom,
- next_centre_real: &self.centre_real,
- next_centre_imag: &self.centre_imag,
- next_zoom: &self.zoom,
- };
-
- video.draw_translation_feedback(self.canvas_width, self.canvas_height, self.scale, &feedback_info);
+ video.draw_translation_feedback(self.canvas_width, self.canvas_height, self.scale, prev_centre, prev_zoom, &self.centre, &self.zoom);
}
- if self.do_textual_feedback { video.draw_textual_feedback(&self.centre_real, &self.centre_imag, &self.zoom, self.max_iter_count) };
+ if self.do_textual_feedback { video.draw_textual_feedback(&self.centre, &self.zoom, self.max_iter_count) };
}
pub fn print_controls() {
println!("Controls:");
- println!("- \u{1B}[1mW\u{1B}[0m Translate up");
- println!("- \u{1B}[1mA\u{1B}[0m Translate left");
- println!("- \u{1B}[1mS\u{1B}[0m Translate down");
- println!("- \u{1B}[1mD\u{1B}[0m Translate right");
+ println!("- \u{1B}[1mW\u{1B}[0m Translate +Im");
+ println!("- \u{1B}[1mA\u{1B}[0m Translate -Re");
+ println!("- \u{1B}[1mS\u{1B}[0m Translate -Im");
+ println!("- \u{1B}[1mD\u{1B}[0m Translate +Re");
println!();
println!("- \u{1B}[1mQ\u{1B}[0m Zoom out");
println!("- \u{1B}[1mE\u{1B}[0m Zoom in");
@@ -135,20 +141,28 @@ impl App {
println!("- \u{1B}[1mR\u{1B}[0m Decrease max. iteration count");
println!("- \u{1B}[1mF\u{1B}[0m Increase max. iteration count");
println!();
- println!("- \u{1B}[1mLeft Alt\u{1B}[0m Cycle to previous fractal");
- println!("- \u{1B}[1mRight Alt\u{1B}[0m Cycle to next fractal");
- println!("- \u{1B}[1mTab\u{1B}[0m Toggle Julia");
- println!("- \u{1B}[1mLeft Ctrl\u{1B}[0m Toggle inverse");
+ println!("- \u{1B}[1mLEFT ALT\u{1B}[0m Cycle to previous fractal");
+ println!("- \u{1B}[1mRIGHT ALT\u{1B}[0m Cycle to next fractal");
+ println!("- \u{1B}[1mTAB\u{1B}[0m Toggle Julia");
+ println!("- \u{1B}[1mLEFT CTRL\u{1B}[0m Toggle inverse");
println!();
- println!("- \u{1B}[1mLeft\u{1B}[0m Cycle to previous palette");
- println!("- \u{1B}[1mRight\u{1B}[0m Cycle to next palette");
- println!("- \u{1B}[1mUp\u{1B}[0m Increase colour range");
- println!("- \u{1B}[1mDown\u{1B}[0m Decrease colour range");
+ println!("- \u{1B}[1mLEFT\u{1B}[0m Cycle to previous palette");
+ println!("- \u{1B}[1mRIGHT\u{1B}[0m Cycle to next palette");
+ println!("- \u{1B}[1mUP\u{1B}[0m Increase colour range");
+ println!("- \u{1B}[1mDOWN\u{1B}[0m Decrease colour range");
println!();
println!("- \u{1B}[1mF1\u{1B}[0m Toggle textual feedback");
println!("- \u{1B}[1mZ\u{1B}[0m Print centre value (c)");
println!();
println!("- \u{1B}[1mC\u{1B}[0m Render frame");
println!();
+ println!("Controls (holding \u{1B}[1mSHIFT\u{1B}[0m):");
+ println!("- \u{1B}[1mW\u{1B}[0m Perturbate/translate +Im");
+ println!("- \u{1B}[1mA\u{1B}[0m Perturbate/translate -Re");
+ println!("- \u{1B}[1mS\u{1B}[0m Perturbate/translate -Im");
+ println!("- \u{1B}[1mD\u{1B}[0m Perturbate/translate +Re");
+ println!();
+ println!("- \u{1B}[1mC\u{1B}[0m Render frame");
+ println!();
}
} \ No newline at end of file
diff --git a/source/benoit/benoit/app/poll_events.rs b/source/benoit/benoit/app/poll_events.rs
index 2ba81fa..af200ec 100644
--- a/source/benoit/benoit/app/poll_events.rs
+++ b/source/benoit/benoit/app/poll_events.rs
@@ -30,8 +30,13 @@ use sdl2::event::Event;
impl App {
#[must_use]
- pub fn poll_events(&mut self, event_pump: &mut EventPump) -> bool {
- for event in event_pump.poll_iter() {
+ pub fn poll_events(&mut self, pump: &mut EventPump) -> bool {
+ loop {
+ let event = match pump.poll_event() {
+ Some(event) => event,
+ None => break,
+ };
+
let quit = match event {
Event::KeyDown {
timestamp: _,
@@ -40,7 +45,11 @@ impl App {
scancode: scan_code,
keymod: _,
repeat: _,
- } => self.handle_keys(scan_code.unwrap()),
+ } => {
+ let state = pump.keyboard_state();
+
+ self.handle_keys(scan_code.unwrap(), state)
+ },
Event::Quit { .. } => true,
_ => false,
};
diff --git a/source/benoit/benoit/app/run.rs b/source/benoit/benoit/app/run.rs
index cc8e3bb..8d6ee0d 100644
--- a/source/benoit/benoit/app/run.rs
+++ b/source/benoit/benoit/app/run.rs
@@ -38,10 +38,7 @@ impl App {
return match self.interactive {
true => self.interactive(),
- false => match self.frame_count {
- 0x1 => self.still(),
- _ => self.animate(),
- },
+ false => self.animate(),
};
}
}
diff --git a/source/benoit/benoit/app/still.rs b/source/benoit/benoit/app/still.rs
deleted file mode 100644
index a5b6cc1..0000000
--- a/source/benoit/benoit/app/still.rs
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- Copyright 2021, 2023 Gabriel Bjørnager Jensen.
-
- This file is part of Benoit.
-
- Benoit is free software: you can redistribute it
- and/or modify it under the terms of the GNU
- Affero General Public License as published by
- the Free Software Foundation, either version 3
- of the License, or (at your option) any later
- version.
-
- Benoit is distributed in the hope that it will
- be useful, but WITHOUT ANY WARRANTY; without
- even the implied warranty of MERCHANTABILITY or
- FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- Affero General Public License for more details.
-
- You should have received a copy of the GNU
- Affero General Public License along with Benoit.
- If not, see <https://www.gnu.org/licenses/>.
-*/
-
-use crate::benoit::app::App;
-use crate::benoit::render::{colour, render};
-
-use std::time::Instant;
-
-impl App {
- pub fn still(&self) -> i32 {
- assert_eq!(self.frame_count, 0x1);
-
- let (mut iter_count_buffer, mut square_dist_buffer, mut image) = App::allocate_buffers(self.canvas_width, self.canvas_height);
-
- eprint!("rendering at {}{:+}i ({}x)...", self.centre_real.to_f32(), self.centre_imag.to_f32(), self.zoom.to_f32());
- let time_start = Instant::now();
-
- render(&mut iter_count_buffer[..], &mut square_dist_buffer[..], self.canvas_width, self.canvas_height, &self.centre_real, &self.centre_imag, &self.zoom, self.max_iter_count, self.inverse, self.point_renderer, self.iterator_function);
- let render_time = time_start.elapsed();
-
- eprint!(" {:.3}ms, colouring...", render_time.as_micros() as f32 / 1000.0);
-
- colour(&mut image[..], self.canvas_width, self.canvas_height, self.multibrot_exponent, self.max_iter_count, self.colour_range, self.palette, &iter_count_buffer[..], &square_dist_buffer[..]);
- let colour_time = time_start.elapsed() - render_time;
-
- eprint!(" {:.3}ms...", colour_time.as_micros() as f32 / 1000.0);
-
- let path = App::image_filename(format!("{}/render.webp", self.dump_path).as_str(), self.image_format);
- self.dump(path.as_str(), &image, self.canvas_width, self.canvas_height);
-
- eprintln!(" done");
-
- return 0x0;
- }
-}
diff --git a/source/benoit/benoit/app/image_filename.rs b/source/benoit/benoit/complex.rs
index 7abc71a..146bd18 100644
--- a/source/benoit/benoit/app/image_filename.rs
+++ b/source/benoit/benoit/complex.rs
@@ -21,17 +21,25 @@
If not, see <https://www.gnu.org/licenses/>.
*/
-use crate::benoit::ImageFormat;
-use crate::benoit::app::App;
-
-impl App {
- #[must_use]
- pub fn image_filename(name: &str, image_format: ImageFormat) -> String {
- let file_extension = match image_format {
- ImageFormat::Png => ".png",
- ImageFormat::Webp => ".webp",
+extern crate rug;
+
+use rug::{Assign, Float};
+
+pub struct Complex {
+ pub real: Float,
+ pub imag: Float,
+}
+
+impl Complex {
+ pub fn clone(&self) -> Self {
+ return Complex {
+ real: self.real.clone(),
+ imag: self.imag.clone(),
};
+ }
- return name.to_owned() + file_extension;
+ pub fn assign(&mut self, other: &Self) {
+ self.real.assign(&other.real);
+ self.imag.assign(&other.imag);
}
}
diff --git a/source/benoit/benoit/configuration.rs b/source/benoit/benoit/configuration.rs
index 2514ef8..7e0c97a 100644
--- a/source/benoit/benoit/configuration.rs
+++ b/source/benoit/benoit/configuration.rs
@@ -21,37 +21,37 @@
If not, see <https://www.gnu.org/licenses/>.
*/
-use crate::benoit::{ImageFormat, PRECISION};
+use crate::benoit::{fractal, ImageFormat, PRECISION};
use crate::benoit::fractal::Fractal;
use crate::benoit::palette::Palette;
-use crate::benoit::rendering::Rendering;
+use crate::benoit::renderer::Renderer;
extern crate rug;
-extern crate toml;
use rug::Float;
-use std::fs::read;
-use std::str::FromStr;
-use toml::{Table, Value};
+
+pub mod load;
pub struct Configuration {
pub thread_count: u32,
- pub fractal: Fractal,
- pub rendering: Rendering,
+ pub fractal: Fractal,
+ pub renderer: Renderer,
pub canvas_width: u32,
pub canvas_height: u32,
pub scale: u32,
- pub frame_count: u32,
+ pub frame_start: u32,
+ pub frame_stop: u32,
pub centre_real: Float,
pub centre_imag: Float,
pub zoom: Float,
- pub max_iter_count: u32,
+ pub extra_real: Float,
+ pub extra_imag: Float,
- pub inverse: bool,
+ pub max_iter_count: u32,
pub palette: Palette,
pub colour_range: f32,
@@ -63,29 +63,41 @@ pub struct Configuration {
}
impl Configuration {
+ pub const DEFAULT_CENTRE_REAL: f64 = 0.0;
+ pub const DEFAULT_CENTRE_IMAG: f64 = 0.0;
+ pub const DEFAULT_ZOOM: f64 = 1.0;
+
+ pub const DEFAULT_EXTRA_REAL: f64 = 0.0;
+ pub const DEFAULT_EXTRA_IMAG: f64 = 0.0;
+
+ pub const DEFAULT_MAX_ITER_COUNT: u32 = 0x100;
+ pub const DEFAULT_COLOUR_RANGE: f32 = 64.0;
+
#[must_use]
pub fn default() -> Configuration {
return Configuration {
thread_count: 0x0,
- fractal: Fractal::Mandelbrot,
- rendering: Rendering::Normal,
+ fractal: Fractal::new(fractal::Kind::Mandelbrot, false),
+ renderer: Renderer::Normal,
canvas_width: 0x100,
canvas_height: 0xC0,
scale: 0x2,
- frame_count: 0x10,
+ frame_start: 0x10,
+ frame_stop: 0x10,
- centre_real: Float::with_val(PRECISION, 0.0),
- centre_imag: Float::with_val(PRECISION, 0.0),
- zoom: Float::with_val(PRECISION, 1.0),
+ centre_real: Float::with_val(PRECISION, Self::DEFAULT_CENTRE_REAL),
+ centre_imag: Float::with_val(PRECISION, Self::DEFAULT_CENTRE_IMAG),
+ zoom: Float::with_val(PRECISION, Self::DEFAULT_ZOOM),
- max_iter_count: 0x100,
+ extra_real: Float::with_val(PRECISION, Self::DEFAULT_EXTRA_REAL),
+ extra_imag: Float::with_val(PRECISION, Self::DEFAULT_EXTRA_IMAG),
- inverse: false,
+ max_iter_count: Self::DEFAULT_MAX_ITER_COUNT,
palette: Palette::Fire,
- colour_range: 64.0,
+ colour_range: Self::DEFAULT_COLOUR_RANGE,
dump_path: "./render".to_string(),
image_format: ImageFormat::Png,
@@ -93,142 +105,4 @@ impl Configuration {
interactive: true,
};
}
-
- #[must_use]
- pub fn load(path: &str) -> Configuration {
- eprintln!("loading configuration at \"{path}\"");
-
- let mut configuration = Configuration::default();
-
- configuration.interactive = false;
-
- let configuration_text = match read(path) {
- Ok(content) => String::from_utf8_lossy(&content).to_string(),
- Err(..) => panic!("unable to read configuration file"),
- };
-
- let configuration_table = Table::from_str(configuration_text.as_str()).expect("unable to parse configuration");
-
- get_integer(&mut configuration.thread_count, &configuration_table, "thread_count");
-
- if let Some(name) = get_string(&configuration_table, "fractal") {
- configuration.fractal = match name.as_str() {
- "burningship" => Fractal::BurningShip,
- "mandelbrot" => Fractal::Mandelbrot,
- "multibrot3" => Fractal::Multibrot3,
- "tricorn" => Fractal::Tricorn,
- name => panic!("invalid fractal kind \"{name}\""),
- }
- }
-
- if let Some(name) = get_string(&configuration_table, "rendering") {
- configuration.rendering = match name.as_str() {
- "julia" => Rendering::Julia,
- "normal" => Rendering::Normal,
- name => panic!("invalid rendering method \"{name}\""),
- };
- }
-
- get_integer(&mut configuration.canvas_width, &configuration_table, "canvas_width");
- get_integer(&mut configuration.canvas_height, &configuration_table, "canvas_height");
- get_integer(&mut configuration.frame_count, &configuration_table, "frame_count");
-
- get_bigfloat(&mut configuration.centre_real, &configuration_table, "real");
- get_bigfloat(&mut configuration.centre_imag, &configuration_table, "imaginary");
- get_bigfloat(&mut configuration.zoom, &configuration_table, "zoom");
-
- get_integer(&mut configuration.max_iter_count, &configuration_table, "maximum_iteration_count");
-
- if let Some(name) = get_string(&configuration_table, "palette") {
- configuration.palette = match name.as_str() {
- "ancient" => Palette::Ancient,
- "fire" => Palette::Fire,
- "greyscale" => Palette::Greyscale,
- "hsv" => Palette::Hsv,
- "lch" => Palette::Lch,
- "sapphire" => Palette::Sapphire,
- name => panic!("invalid palette \"{name}\""),
- };
- }
-
- get_float(&mut configuration.colour_range, &configuration_table, "colour_range");
-
- if let Some(path) = get_string(&configuration_table, "dump_path") {
- configuration.dump_path = path.clone();
- }
-
- if let Some(name) = get_string(&configuration_table, "image_format") {
- configuration.image_format = match name.as_str() {
- "png" => ImageFormat::Png,
- "webp" => ImageFormat::Webp,
- name => panic!("invalid image format \"{name}\""),
- };
- }
-
- match check_configuration(&configuration) {
- Err(message) => panic!("invalid configuration: {message}"),
- _ => {},
- }
-
- return configuration;
- }
-}
-
-fn check_configuration(configuration: &Configuration) -> Result<(), &str> {
- // We allow thread counts of zero as those signal
- // automatic thread count detection.
- if configuration.canvas_width == 0x0 {
- return Err("only non-zero values for canvas_width are allowed");
- } else if configuration.scale == 0x0 {
- return Err("only non-zero values for scale are allowed");
- } else if configuration.frame_count == 0x0 {
- return Err("only non-zero values for frame_count are allowed");
- } else if configuration.max_iter_count == 0x0 {
- return Err("only non-zero values for maximum_iteration_count are allowed");
- }
-
- return Ok(());
-}
-
-fn get_value<'a>(table: &'a Table, name: &str) -> Option<&'a Value> {
- if !table.contains_key(name) { return None };
-
- return Some(&table[name]);
-}
-
-fn get_integer(buffer: &mut u32, table: &Table, name: &str) {
- match get_value(table, name) {
- Some(Value::Integer(value)) => *buffer = (*value) as u32,
- Some(_) => panic!("\"{name}\" should be an integer"),
- _ => {},
- };
-}
-
-fn get_float(buffer: &mut f32, table: &Table, name: &str) {
- match get_value(table, name) {
- Some(Value::Float(value)) => *buffer = (*value) as f32,
- Some(_) => panic!("\"{name}\" should be a float"),
- _ => {},
- };
-}
-
-fn get_bigfloat(buffer: &mut Float, table: &Table, name: &str) {
- return match get_value(table, name) {
- Some(Value::String(string)) => {
- *buffer = match Float::parse(string) {
- Ok(value) => Float::with_val(PRECISION, value),
- _ => panic!("invalid format of \"{name}\""),
- }
- },
- Some(_) => panic!("\"{name}“ should be a quoted float"),
- _ => {},
- };
-}
-
-fn get_string(table: &Table, name: &str) -> Option<String> {
- return match get_value(table, name) {
- Some(Value::String(value)) => Some(value.clone()),
- Some(_) => panic!("\"{name}\" should be a string"),
- _ => None,
- };
}
diff --git a/source/benoit/benoit/configuration/load.rs b/source/benoit/benoit/configuration/load.rs
new file mode 100644
index 0000000..3f5ec11
--- /dev/null
+++ b/source/benoit/benoit/configuration/load.rs
@@ -0,0 +1,199 @@
+/*
+ Copyright 2021, 2023 Gabriel Bjørnager Jensen.
+
+ This file is part of Benoit.
+
+ Benoit is free software: you can redistribute it
+ and/or modify it under the terms of the GNU
+ Affero General Public License as published by
+ the Free Software Foundation, either version 3
+ of the License, or (at your option) any later
+ version.
+
+ Benoit is distributed in the hope that it will
+ be useful, but WITHOUT ANY WARRANTY; without
+ even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Affero General Public License for more details.
+
+ You should have received a copy of the GNU
+ Affero General Public License along with Benoit.
+ If not, see <https://www.gnu.org/licenses/>.
+*/
+
+use crate::benoit::{fractal, ImageFormat, PRECISION};
+use crate::benoit::configuration::Configuration;
+use crate::benoit::palette::Palette;
+use crate::benoit::renderer::Renderer;
+
+extern crate rug;
+extern crate toml;
+
+use rug::Float;
+use std::fs::read;
+use std::str::FromStr;
+use toml::{Table, Value};
+
+impl Configuration {
+ #[must_use]
+ pub fn load(path: &str) -> Configuration {
+ eprintln!("loading configuration at \"{path}\"");
+
+ let mut configuration = Configuration::default();
+
+ configuration.interactive = false;
+
+ let configuration_text = match read(path) {
+ Ok(content) => String::from_utf8_lossy(&content).to_string(),
+ Err(..) => panic!("unable to read configuration file"),
+ };
+
+ let configuration_table = Table::from_str(configuration_text.as_str()).expect("unable to parse configuration");
+
+ get_integer(&mut configuration.thread_count, &configuration_table, "thread_count");
+
+ if let Some(name) = get_string(&configuration_table, "renderer") {
+ configuration.renderer = match name.as_str() {
+ "julia" => Renderer::Julia,
+ "normal" => Renderer::Normal,
+ name => panic!("invalid renderer method \"{name}\""),
+ };
+ }
+
+ if let Some(name) = get_string(&configuration_table, "fractal") {
+ configuration.fractal.set_kind(match name.as_str() {
+ "burningship" => fractal::Kind::BurningShip,
+ "mandelbrot" => fractal::Kind::Mandelbrot,
+ "multibrot3" => fractal::Kind::Multibrot3,
+ "tricorn" => fractal::Kind::Tricorn,
+ name => panic!("invalid fractal kind \"{name}\""),
+ });
+ }
+
+ {
+ let mut inverse = false;
+ get_boolean(&mut inverse, &configuration_table, "inverse");
+ configuration.fractal.set_inverse(inverse);
+ }
+
+ get_integer(&mut configuration.canvas_width, &configuration_table, "canvas_width");
+ get_integer(&mut configuration.canvas_height, &configuration_table, "canvas_height");
+ get_integer(&mut configuration.frame_start, &configuration_table, "frame_start");
+ get_integer(&mut configuration.frame_stop, &configuration_table, "frame_stop");
+
+ get_bigfloat(&mut configuration.centre_real, &configuration_table, "real");
+ get_bigfloat(&mut configuration.centre_imag, &configuration_table, "imaginary");
+ get_bigfloat(&mut configuration.extra_real, &configuration_table, "extra_real");
+ get_bigfloat(&mut configuration.extra_imag, &configuration_table, "extra_imaginary");
+ get_bigfloat(&mut configuration.zoom, &configuration_table, "zoom");
+
+ get_integer(&mut configuration.max_iter_count, &configuration_table, "maximum_iteration_count");
+
+ if let Some(name) = get_string(&configuration_table, "palette") {
+ configuration.palette = match name.as_str() {
+ "ancient" => Palette::Ancient,
+ "fire" => Palette::Fire,
+ "greyscale" => Palette::Greyscale,
+ "hsv" => Palette::Hsv,
+ "lch" => Palette::Lch,
+ "sapphire" => Palette::Sapphire,
+ name => panic!("invalid palette \"{name}\""),
+ };
+ }
+
+ get_float(&mut configuration.colour_range, &configuration_table, "colour_range");
+
+ if let Some(path) = get_string(&configuration_table, "dump_path") {
+ configuration.dump_path = path.clone();
+ }
+
+ if let Some(name) = get_string(&configuration_table, "image_format") {
+ configuration.image_format = match name.as_str() {
+ "png" => ImageFormat::Png,
+ "webp" => ImageFormat::Webp,
+ name => panic!("invalid image format \"{name}\""),
+ };
+ }
+
+ match check_configuration(&configuration) {
+ Err(message) => panic!("invalid configuration: {message}"),
+ _ => {},
+ }
+
+ return configuration;
+ }
+}
+
+fn check_configuration(configuration: &Configuration) -> Result<(), &str> {
+ // We allow thread counts of zero as those signal
+ // automatic thread count detection.
+
+ if configuration.canvas_width == 0x0 {
+ return Err("only non-zero values for canvas_width are allowed");
+ }
+
+ if configuration.scale == 0x0 {
+ return Err("only non-zero values for scale are allowed");
+ }
+
+ if configuration.frame_start > configuration.frame_stop {
+ return Err("frame_start may not be greater than frame_stop");
+ }
+
+ if configuration.max_iter_count == 0x0 {
+ return Err("only non-zero values for maximum_iteration_count are allowed");
+ }
+
+ return Ok(());
+}
+
+fn get_value<'a>(table: &'a Table, name: &str) -> Option<&'a Value> {
+ if !table.contains_key(name) { return None };
+
+ return Some(&table[name]);
+}
+
+fn get_boolean(buffer: &mut bool, table: &Table, name: &str) {
+ match get_value(table, name) {
+ Some(Value::Boolean(value)) => *buffer = *value,
+ Some(_) => panic!("\"{name}\" should be a boolean"),
+ _ => {},
+ };
+}
+
+fn get_integer(buffer: &mut u32, table: &Table, name: &str) {
+ match get_value(table, name) {
+ Some(Value::Integer(value)) => *buffer = (*value) as u32,
+ Some(_) => panic!("\"{name}\" should be an integer"),
+ _ => {},
+ };
+}
+
+fn get_float(buffer: &mut f32, table: &Table, name: &str) {
+ match get_value(table, name) {
+ Some(Value::Float(value)) => *buffer = (*value) as f32,
+ Some(_) => panic!("\"{name}\" should be a float"),
+ _ => {},
+ };
+}
+
+fn get_bigfloat(buffer: &mut Float, table: &Table, name: &str) {
+ return match get_value(table, name) {
+ Some(Value::String(string)) => {
+ *buffer = match Float::parse(string) {
+ Ok(value) => Float::with_val(PRECISION, value),
+ _ => panic!("invalid format of \"{name}\""),
+ }
+ },
+ Some(_) => panic!("\"{name}“ should be a quoted float"),
+ _ => {},
+ };
+}
+
+fn get_string(table: &Table, name: &str) -> Option<String> {
+ return match get_value(table, name) {
+ Some(Value::String(value)) => Some(value.clone()),
+ Some(_) => panic!("\"{name}\" should be a string"),
+ _ => None,
+ };
+}
diff --git a/source/benoit/benoit/fractal.rs b/source/benoit/benoit/fractal.rs
index 5cefcfe..7ca6035 100644
--- a/source/benoit/benoit/fractal.rs
+++ b/source/benoit/benoit/fractal.rs
@@ -25,10 +25,15 @@ use crate::benoit::render::IteratorFunction;
use crate::benoit::render::iterate;
use std::mem::transmute;
-use std::ops::Add;
+
+pub struct Fractal {
+ kind: Kind,
+ inverse: bool,
+}
#[derive(Clone, Copy)]
-pub enum Fractal {
+#[repr(u8)]
+pub enum Kind {
BurningShip,
Mandelbrot,
Multibrot3,
@@ -36,54 +41,80 @@ pub enum Fractal {
}
impl Fractal {
- const MAX: u8 = Fractal::Tricorn as u8;
-
- pub fn get_name(self) -> &'static str {
- return match self {
- Fractal::BurningShip => "burning ship",
- Fractal::Mandelbrot => "mandelbrot set",
- Fractal::Multibrot3 => "multibrot (d=3)",
- Fractal::Tricorn => "tricorn",
+ pub const fn new(kind: Kind, inverse: bool) -> Self {
+ let fractal = Fractal {
+ kind: kind,
+ inverse: inverse,
};
+
+ return fractal;
}
- pub fn get_exponent(self) -> f32 {
- return match self {
- Fractal::BurningShip => 2.0,
- Fractal::Mandelbrot => 2.0,
- Fractal::Multibrot3 => 3.0,
- Fractal::Tricorn => 2.0,
+ #[must_use]
+ pub fn kind(&self) -> Kind {
+ return self.kind;
+ }
+
+ #[must_use]
+ pub fn inverse(&self) -> bool {
+ return self.inverse;
+ }
+
+ #[must_use]
+ pub fn exponent(&self) -> f32 {
+ return match self.kind {
+ Kind::BurningShip => 2.0,
+ Kind::Mandelbrot => 2.0,
+ Kind::Multibrot3 => 3.0,
+ Kind::Tricorn => 2.0,
};
}
- pub fn get_iterator(self) -> IteratorFunction {
- return match self {
- Fractal::BurningShip => iterate::burning_ship,
- Fractal::Mandelbrot => iterate::mandelbrot,
- Fractal::Multibrot3 => iterate::multibrot3,
- Fractal::Tricorn => iterate::tricorn,
+ pub fn iterator(&self) -> IteratorFunction {
+ return match self.kind {
+ Kind::BurningShip => iterate::burning_ship,
+ Kind::Mandelbrot => iterate::mandelbrot,
+ Kind::Multibrot3 => iterate::multibrot3,
+ Kind::Tricorn => iterate::tricorn,
};
}
-}
+ pub fn set_kind(&mut self, kind: Kind) {
+ self.kind = kind;
+ }
-impl Add<i8> for Fractal {
- type Output = Fractal;
+ pub fn set_inverse(&mut self, inverse: bool) {
+ self.inverse = inverse;
+ }
- fn add(self, direction: i8) -> Self {
- assert!(direction != 0x0);
+ pub fn cycle(&mut self, direction: i8) {
+ // Not important.
+ debug_assert!(direction != 0x0);
- let raw = self as i8 + direction;
+ let raw = self.kind as i8 + direction;
let raw: u8 = if raw < 0x0 {
- Fractal::MAX
- } else if raw > Fractal::MAX as i8 {
+ Kind::MAX
+ } else if raw > Kind::MAX as i8 {
0x0
} else {
raw as u8
};
- let new: Self = unsafe { transmute(raw) };
+ let new: Kind = unsafe { transmute(raw) };
+
+ self.kind = new;
+ }
+}
+
+impl Kind {
+ const MAX: u8 = Kind::Tricorn as u8;
- return new;
+ pub fn name(self) -> &'static str {
+ return match self {
+ Kind::BurningShip => "burning ship",
+ Kind::Mandelbrot => "mandelbrot set",
+ Kind::Multibrot3 => "multibrot (d=3) set",
+ Kind::Tricorn => "tricorn",
+ };
}
}
diff --git a/source/benoit/benoit/palette.rs b/source/benoit/benoit/palette.rs
index 5477242..d270682 100644
--- a/source/benoit/benoit/palette.rs
+++ b/source/benoit/benoit/palette.rs
@@ -62,7 +62,7 @@ static mut DATA_SAPPHIRE: PaletteData = default_palette_data();
fn calculate_palettes() {
for palette in all::<Palette>() {
let data = palette.get_data_mut();
- let function = palette.get_function();
+ let function = palette.function();
for index in 0x0..PALETTE_DATA_LENGTH {
let factor = index as f32 / PALETTE_DATA_LENGTH as f32;
@@ -79,7 +79,7 @@ impl Palette {
const MAX: Self = Palette::Sapphire;
#[must_use]
- pub fn get_name(self) -> &'static str {
+ pub fn name(self) -> &'static str {
return match self {
Palette::Ancient => "ancient",
Palette::Fire => "fire",
@@ -111,7 +111,7 @@ impl Palette {
}
#[must_use]
- fn get_function(self) -> fn(f32) -> (f32, f32, f32) {
+ fn function(self) -> fn(f32) -> (f32, f32, f32) {
return match self {
Palette::Ancient => paint::ancient,
Palette::Fire => paint::fire,
diff --git a/source/benoit/benoit/render.rs b/source/benoit/benoit/render.rs
index 45da9ee..b81679c 100644
--- a/source/benoit/benoit/render.rs
+++ b/source/benoit/benoit/render.rs
@@ -21,22 +21,14 @@
If not, see <https://www.gnu.org/licenses/>.
*/
+use crate::benoit::complex::Complex;
use crate::benoit::render::render_data::RenderData;
-extern crate rug;
-
-use rug::Float;
-
-pub mod colour;
pub mod colour_data;
pub mod iterate;
-pub mod render;
pub mod render_data;
pub mod render_point;
-pub use colour::*;
-pub use render::*;
-
-pub type IteratorFunction = fn(&mut Float, &mut Float, &Float, &Float);
+pub type IteratorFunction = fn(&mut Complex, &Complex);
pub type PointRenderer = fn(&RenderData, u32, u32, IteratorFunction) -> (u32, f32);
diff --git a/source/benoit/benoit/render/colour.rs b/source/benoit/benoit/render/colour.rs
deleted file mode 100644
index 9debac4..0000000
--- a/source/benoit/benoit/render/colour.rs
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- Copyright 2021, 2023 Gabriel Bjørnager Jensen.
-
- This file is part of Benoit.
-
- Benoit is free software: you can redistribute it
- and/or modify it under the terms of the GNU
- Affero General Public License as published by
- the Free Software Foundation, either version 3
- of the License, or (at your option) any later
- version.
-
- Benoit is distributed in the hope that it will
- be useful, but WITHOUT ANY WARRANTY; without
- even the implied warranty of MERCHANTABILITY or
- FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- Affero General Public License for more details.
-
- You should have received a copy of the GNU
- Affero General Public License along with Benoit.
- If not, see <https://www.gnu.org/licenses/>.
-*/
-
-use crate::benoit::palette::{Palette, PALETTE_DATA_LENGTH};
-use crate::benoit::render::colour_data::ColourData;
-
-extern crate rayon;
-
-use rayon::prelude::*;
-use std::sync::Arc;
-
-pub fn colour(buffer: &mut [u8], canvas_width: u32, canvas_height: u32, multibrot_exponent: f32, max_iter_count: u32, colour_range: f32, palette: Palette, iter_count_buffer: &[u32], square_dist_buffer: &[f32]) {
- let data = Arc::new(ColourData::new(buffer, canvas_width, canvas_height, multibrot_exponent, max_iter_count, colour_range, palette, iter_count_buffer, square_dist_buffer));
-
- (0x0..canvas_height).into_par_iter().for_each(|row| {
- colour_row(data.clone(), row as u32);
- });
-}
-
-fn colour_row(data: Arc<ColourData>, y: u32) {
- let (iter_count_buffer, square_dist_buffer) = data.input_buffers(y);
-
- let image = data.output_buffers(y);
-
- let (canvas_width, exponent, max_iter_count, colour_range, palette_data) = data.consts();
-
- for x in 0x0..canvas_width {
- let x = x as usize;
-
- let iter_count = unsafe { *iter_count_buffer.get_unchecked( x) };
- let distance = unsafe { *square_dist_buffer.get_unchecked(x) }.sqrt();
-
- let (red, green, blue) = if iter_count < max_iter_count {
- let factor = (iter_count as f32 + 1.0 - distance.log(exponent).log(exponent)) / colour_range;
-
- let index = (factor * PALETTE_DATA_LENGTH as f32).round() as usize % PALETTE_DATA_LENGTH;
- unsafe { *palette_data.get_unchecked(index) }
- } else {
- (0.0, 0.0, 0.0)
- };
-
- let red = (red * 255.0).round() as u8;
- let green = (green * 255.0).round() as u8;
- let blue = (blue * 255.0).round() as u8;
-
- unsafe {
- let x = x * 0x3;
-
- *image.get_unchecked_mut(x) = red;
- *image.get_unchecked_mut(x + 0x1) = green;
- *image.get_unchecked_mut(x + 0x2) = blue;
- }
- }
-}
diff --git a/source/benoit/benoit/render/colour_data.rs b/source/benoit/benoit/render/colour_data.rs
index 33123db..114a8b0 100644
--- a/source/benoit/benoit/render/colour_data.rs
+++ b/source/benoit/benoit/render/colour_data.rs
@@ -26,8 +26,7 @@ use crate::benoit::palette::{Palette, PaletteData};
use std::slice::{from_raw_parts, from_raw_parts_mut};
pub struct ColourData {
- canvas_width: u32,
- canvas_height: u32,
+ canvas_size: usize,
exponent: f32,
max_iter_count: u32,
@@ -45,8 +44,7 @@ impl ColourData {
#[must_use]
pub fn new(image: &mut [u8], canvas_width: u32, canvas_height: u32, exponent: f32, max_iter_count: u32, colour_range: f32, palette: Palette, iter_count_buffer: &[u32], square_dist_buffer: &[f32]) -> ColourData {
return ColourData {
- canvas_width: canvas_width,
- canvas_height: canvas_height,
+ canvas_size: canvas_height as usize * canvas_width as usize,
exponent: exponent,
max_iter_count: max_iter_count,
@@ -62,31 +60,23 @@ impl ColourData {
}
#[must_use]
- pub fn input_buffers(&self, row: u32) -> (&[u32], &[f32]) {
- assert!(row < self.canvas_height);
-
- let offset = row as usize * self.canvas_width as usize;
-
- let iter_count = unsafe { from_raw_parts(self.iter_count_buffer.add(offset), self.canvas_width as usize) };
- let dist = unsafe { from_raw_parts(self.square_dist_buffer.add(offset), self.canvas_width as usize) };
+ pub fn input_buffers(&self) -> (&[u32], &[f32]) {
+ let iter_count = unsafe { from_raw_parts(self.iter_count_buffer, self.canvas_size) };
+ let dist = unsafe { from_raw_parts(self.square_dist_buffer, self.canvas_size) };
return (iter_count, dist);
}
#[must_use]
- pub fn output_buffers(&self, row: u32) -> &mut [u8] {
- assert!(row < self.canvas_height);
-
- let offset = row as usize * self.canvas_width as usize * 0x3;
-
- let image = unsafe { from_raw_parts_mut(self.image.add(offset), self.canvas_width as usize * 0x3) };
+ pub fn output_buffers(&self) -> &mut [u8] {
+ let image = unsafe { from_raw_parts_mut(self.image, self.canvas_size * 0x3) };
return image;
}
#[must_use]
- pub fn consts(&self) -> (u32, f32, u32, f32, &'static PaletteData) {
- return (self.canvas_width, self.exponent, self.max_iter_count, self.colour_range, self.palette_data);
+ pub fn consts(&self) -> (f32, u32, f32, &'static PaletteData) {
+ return (self.exponent, self.max_iter_count, self.colour_range, self.palette_data);
}
}
diff --git a/source/benoit/benoit/render/iterate/burning_ship.rs b/source/benoit/benoit/render/iterate/burning_ship.rs
index 43e6916..9e4f029 100644
--- a/source/benoit/benoit/render/iterate/burning_ship.rs
+++ b/source/benoit/benoit/render/iterate/burning_ship.rs
@@ -21,27 +21,25 @@
If not, see <https://www.gnu.org/licenses/>.
*/
-extern crate rug;
+use crate::benoit::complex::Complex;
-use rug::Float;
-
-pub fn burning_ship(za: &mut Float, zb: &mut Float, ca: &Float, cb: &Float) {
+pub fn burning_ship(z: &mut Complex, c: &Complex) {
// The Burning Ship is different in that - during
// iteration - the real and imaginary parts of (z)
// are made absolute:
//
// z(n+1) = (abs(Re(z(n)))+i*abs(Im(z(n))))^2+c.
- za.abs_mut();
- zb.abs_mut();
+ z.real.abs_mut(); // abs(a)
+ z.imag.abs_mut(); // abs(b)
- let za_temporary = za.clone();
+ let za_temporary = z.real.clone(); // abs(a)
- za.square_mut();
- *za -= &*zb * &*zb;
- *za += ca;
+ z.real.square_mut(); // abs(a)^2
+ z.real -= &z.imag * &z.imag; // abs(a)^2-abs(b)^2
+ z.real += &c.real; // abs(a)^2-abs(b)^2+Re(c)
- *zb *= za_temporary;
- *zb *= 2.0;
- *zb += cb;
+ z.imag *= &za_temporary; // abs(a)
+ z.imag *= 2.0; // 2*abs(a)
+ z.imag += &c.imag; // 2*abs(a)+Im(c)
}
diff --git a/source/benoit/benoit/render/iterate/mandelbrot.rs b/source/benoit/benoit/render/iterate/mandelbrot.rs
index 015e2e7..078cc18 100644
--- a/source/benoit/benoit/render/iterate/mandelbrot.rs
+++ b/source/benoit/benoit/render/iterate/mandelbrot.rs
@@ -21,11 +21,9 @@
If not, see <https://www.gnu.org/licenses/>.
*/
-extern crate rug;
+use crate::benoit::complex::Complex;
-use rug::Float;
-
-pub fn mandelbrot(za: &mut Float, zb: &mut Float, ca: &Float, cb: &Float) {
+pub fn mandelbrot(z: &mut Complex, c: &Complex) {
// The Mandelbrot Set (M) is defined as the set of
// values in the complex plane where the iterating
// function
@@ -36,7 +34,7 @@ pub fn mandelbrot(za: &mut Float, zb: &mut Float, ca: &Float, cb: &Float) {
//
// abs(z) = sqrt(Re(z)^2+Im(z)^2) <= 2^2 = 4.
- let za_temporary = za.clone(); // a
+ let za_temporary = z.real.clone(); // a
// We can calculate the square of a complex number
// as:
@@ -46,11 +44,11 @@ pub fn mandelbrot(za: &mut Float, zb: &mut Float, ca: &Float, cb: &Float) {
// = a^2+abi+abi-b^2
// = a^2-b^2+2abi.
- za.square_mut(); // a^2
- *za -= &*zb * &*zb; // a^2-b^2
- *za += ca; // a^2-b^2+Re(c)
+ z.real.square_mut(); // a^2
+ z.real -= &z.imag * &z.imag; // a^2-b^2
+ z.real += &c.real; // a^2-b^2+Re(c)
- *zb *= za_temporary; // ab
- *zb *= 2.0; // 2ab
- *zb += cb; // 2ab+Im(c)
+ z.imag *= &za_temporary; // ab
+ z.imag *= 2.0; // 2ab
+ z.imag += &c.imag; // 2ab+Im(c)
}
diff --git a/source/benoit/benoit/render/iterate/multibrot3.rs b/source/benoit/benoit/render/iterate/multibrot3.rs
index d0a7c2f..fc6f872 100644
--- a/source/benoit/benoit/render/iterate/multibrot3.rs
+++ b/source/benoit/benoit/render/iterate/multibrot3.rs
@@ -21,14 +21,16 @@
If not, see <https://www.gnu.org/licenses/>.
*/
+use crate::benoit::complex::Complex;
+
use crate::benoit::PRECISION;
extern crate rug;
use rug::Float;
-pub fn multibrot3(za: &mut Float, zb: &mut Float, ca: &Float, cb: &Float) {
- let za_temporary = za.clone(); // a
+pub fn multibrot3(z: &mut Complex, c: &Complex) {
+ let za_temporary = z.real.clone(); // a
// (a+bi)^3
// = (a+bi)(a+bi)(a+bi)
@@ -39,22 +41,22 @@ pub fn multibrot3(za: &mut Float, zb: &mut Float, ca: &Float, cb: &Float) {
// <=> z_a = a^3-3ab^2
// z_b = 3(a^2)b-b^3
- let mut temporary0 = Float::with_val(PRECISION, &*zb * &*zb); // b^2
+ let mut temporary0 = Float::with_val(PRECISION, &z.imag * &z.imag); // b^2
- let temporary1 = Float::with_val(PRECISION, &temporary0 * &*zb); // b^3
+ let temporary1 = Float::with_val(PRECISION, &temporary0 * &z.imag); // b^3
- temporary0 *= &*za; // ab^2
- temporary0 *= 0x3; // 3ab^2
+ temporary0 *= &z.real; // ab^2
+ temporary0 *= 0x3; // 3ab^2
- za.square_mut(); // a^2
+ z.real.square_mut(); // a^2
- *zb *= &*za; // (a^2)b
+ z.imag *= &z.real; // (a^2)b
- *za *= &za_temporary; // a^3
- *za -= &temporary0; // a^3-3ab^2
- *za += ca; // a^3-3ab^2+Re(c)
+ z.real *= &za_temporary; // a^3
+ z.real -= &temporary0; // a^3-3ab^2
+ z.real += &c.real; // a^3-3ab^2+Re(c)
- *zb *= 3.0; // 3(a^2)b
- *zb -= &temporary1; // 3(a^2)b-b^3
- *zb += cb; // 3(a^2)b-b^3+Im(c)
+ z.imag *= 3.0; // 3(a^2)b
+ z.imag -= &temporary1; // 3(a^2)b-b^3
+ z.imag += &c.imag; // 3(a^2)b-b^3+Im(c)
}
diff --git a/source/benoit/benoit/render/iterate/tricorn.rs b/source/benoit/benoit/render/iterate/tricorn.rs
index 2f11ecf..b562d66 100644
--- a/source/benoit/benoit/render/iterate/tricorn.rs
+++ b/source/benoit/benoit/render/iterate/tricorn.rs
@@ -21,30 +21,28 @@
If not, see <https://www.gnu.org/licenses/>.
*/
-extern crate rug;
+use crate::benoit::complex::Complex;
-use rug::Float;
-
-pub fn tricorn(za: &mut Float, zb: &mut Float, ca: &Float, cb: &Float) {
+pub fn tricorn(z: &mut Complex, c: &Complex) {
// The Tricorn is only different from the
// Mandelbrot Set in that the conjugate of (z) is
// used instead of just (z):
//
// z(n+1) = (Re(z(n))-Im(z(n))i)^2+c.
- let za_temporary = za.clone(); // a
+ let za_temporary = z.real.clone(); // a
- za.square_mut(); // a^2
- *za -= &*zb * &*zb; // a^2-b^2
- *za += ca; // a^2
+ z.real.square_mut(); // a^2
+ z.real -= &z.imag * &z.imag; // a^2-b^2
+ z.real += &c.real; // a^2
- *zb *= za_temporary;
+ z.imag *= &za_temporary;
// We can negate the value by multiplying with
// (-1). A multiplication can be saved, as
//
// a*2*(-1) = a*(-2).
//
// Thus, we may combine these two multiplications.
- *zb *= -2.0;
- *zb += cb;
+ z.imag *= -2.0;
+ z.imag += &c.imag;
}
diff --git a/source/benoit/benoit/render/render_data.rs b/source/benoit/benoit/render/render_data.rs
index a1d2fdd..86a21ee 100644
--- a/source/benoit/benoit/render/render_data.rs
+++ b/source/benoit/benoit/render/render_data.rs
@@ -22,6 +22,7 @@
*/
use crate::benoit::{PRECISION, width_height_ratio};
+use crate::benoit::complex::Complex;
extern crate rug;
@@ -34,9 +35,9 @@ pub struct RenderData {
canvas_size: usize,
- centre_real: Float,
- centre_imag: Float,
- zoom: Float,
+ centre: Complex,
+ extra: Complex,
+ zoom: Float,
max_iter_count: u32,
@@ -54,7 +55,17 @@ pub struct RenderData {
impl RenderData {
#[must_use]
- pub fn new(iter_count_buffer: &mut [u32], square_dist_buffer: &mut [f32], canvas_width: u32, canvas_height: u32, centre_real: Float, centre_imag: Float, zoom: Float, max_iter_count: u32, inverse: bool) -> RenderData {
+ pub fn new(
+ iter_count_buffer: &mut [u32],
+ square_dist_buffer: &mut [f32],
+ canvas_width: u32,
+ canvas_height: u32,
+ centre: Complex,
+ extra: Complex,
+ zoom: Float,
+ max_iter_count: u32,
+ inverse: bool,
+ ) -> RenderData {
let (width_ratio, height_ratio) = width_height_ratio(canvas_width, canvas_height);
return RenderData {
@@ -63,9 +74,9 @@ impl RenderData {
canvas_size: canvas_height as usize * canvas_width as usize,
- centre_real: centre_real,
- centre_imag: centre_imag,
- zoom: zoom,
+ centre: centre,
+ extra: extra,
+ zoom: zoom,
max_iter_count: max_iter_count,
@@ -83,10 +94,10 @@ impl RenderData {
}
#[must_use]
- pub fn inverse_factor(&self, a: &Float, b: &Float) -> Float {
+ pub fn inverse_factor(&self, val: &Complex) -> Float {
return if self.inverse {
- let mut inverse_factor = Float::with_val(PRECISION, a * a);
- inverse_factor += b * b;
+ let mut inverse_factor = Float::with_val(PRECISION, &val.real * &val.real);
+ inverse_factor += &val.imag * &val.imag;
inverse_factor.recip_mut();
inverse_factor
@@ -101,8 +112,8 @@ impl RenderData {
}
#[must_use]
- pub fn input(&self) -> (&Float, &Float, &Float, u32) {
- return (&self.centre_real, &self.centre_imag, &self.zoom, self.max_iter_count);
+ pub fn input(&self) -> (&Complex, &Complex, &Float, u32) {
+ return (&self.centre, &self.extra, &self.zoom, self.max_iter_count);
}
#[must_use]
diff --git a/source/benoit/benoit/render/render_point/julia.rs b/source/benoit/benoit/render/render_point/julia.rs
index fa7329c..63cd4cb 100644
--- a/source/benoit/benoit/render/render_point/julia.rs
+++ b/source/benoit/benoit/render/render_point/julia.rs
@@ -22,6 +22,7 @@
*/
use crate::benoit::{BAILOUT, PRECISION};
+use crate::benoit::complex::Complex;
use crate::benoit::render::IteratorFunction;
use crate::benoit::render::render_data::RenderData;
@@ -33,41 +34,48 @@ use rug::float::Special;
pub fn julia(data: &RenderData, x: u32, y: u32, iterator: IteratorFunction) -> (u32, f32) {
// For more information, see render_point::normal.
- let (centre_real, centre_imag, _zoom, max_iter_count) = data.input();
+ let (centre, extra, zoom, max_iter_count) = data.input();
let (x_offset, y_offset, x_factor, y_factor) = data.consts();
let x_temporary = (x as f32 + x_offset) * x_factor;
let y_temporary = (y as f32 + y_offset) * y_factor;
- let ca = centre_real;
- let cb = centre_imag;
+ let c = extra;
- let mut za = Float::with_val(PRECISION, x_temporary);
- let mut zb = Float::with_val(PRECISION, y_temporary);
+ let mut z = {
+ let mut a = Float::with_val(PRECISION, x_temporary / zoom);
+ a += &centre.real;
- let inverse_factor = data.inverse_factor(&za, &zb);
+ let mut b = Float::with_val(PRECISION, y_temporary / zoom);
+ b -= &centre.imag;
- za *= &inverse_factor;
- zb *= &inverse_factor;
+ Complex {real: a, imag: b}
+ };
- let mut za_prev = Float::with_val(PRECISION, Special::Nan);
- let mut zb_prev = Float::with_val(PRECISION, Special::Nan);
+ let inverse_factor = data.inverse_factor(&z);
+
+ z.real *= &inverse_factor;
+ z.imag *= &inverse_factor;
+
+ let mut z_prev = Complex {
+ real: Float::with_val(PRECISION, Special::Nan),
+ imag: Float::with_val(PRECISION, Special::Nan),
+ };
let mut iter_count: u32 = 0x1;
let mut square_dist = Float::with_val(PRECISION, Special::Nan);
while {
- square_dist.assign(&za * &za + &zb * &zb);
+ square_dist.assign(&z.real * &z.real + &z.imag * &z.imag);
- let periodic = za == za_prev && zb == zb_prev;
+ let periodic = z.real == z_prev.real && z.imag == z_prev.imag;
if periodic { iter_count = max_iter_count }
square_dist <= BAILOUT && iter_count < max_iter_count
} {
- za_prev.assign(&za);
- zb_prev.assign(&zb);
+ z_prev.assign(&z);
- iterator(&mut za, &mut zb, ca, cb);
+ iterator(&mut z, c);
iter_count += 0x1;
}
diff --git a/source/benoit/benoit/render/render_point/normal.rs b/source/benoit/benoit/render/render_point/normal.rs
index 8bf88f3..167bdb6 100644
--- a/source/benoit/benoit/render/render_point/normal.rs
+++ b/source/benoit/benoit/render/render_point/normal.rs
@@ -22,6 +22,7 @@
*/
use crate::benoit::{BAILOUT, PRECISION};
+use crate::benoit::complex::Complex;
use crate::benoit::render::IteratorFunction;
use crate::benoit::render::render_data::RenderData;
@@ -31,56 +32,55 @@ use rug::{Assign, Float};
use rug::float::Special;
pub fn normal(data: &RenderData, x: u32, y: u32, iterator: IteratorFunction) -> (u32, f32) {
- let (centre_real, centre_imag, zoom, max_iter_count) = data.input();
+ let (centre, extra, zoom, max_iter_count) = data.input();
let (x_offset, y_offset, x_factor, y_factor) = data.consts();
let x_temporary = (x as f32 + x_offset) * x_factor;
let y_temporary = (y as f32 + y_offset) * y_factor;
- let ca = {
- let mut ca = Float::with_val(PRECISION, x_temporary / zoom);
- ca += centre_real;
+ let mut z = {
+ let mut a = Float::with_val(PRECISION, x_temporary / zoom);
+ a += &centre.real;
- ca
- };
-
- let cb = {
- let mut cb = Float::with_val(PRECISION, y_temporary / zoom);
- cb -= centre_imag;
+ let mut b = Float::with_val(PRECISION, y_temporary / zoom);
+ b -= &centre.imag;
- cb
+ Complex {real: a, imag: b}
};
- let inverse_factor = data.inverse_factor(&ca, &cb);
+ let inverse_factor = data.inverse_factor(&z);
- let ca = ca * &inverse_factor;
- let cb = cb * &inverse_factor;
+ z.real *= &inverse_factor;
+ z.imag *= &inverse_factor;
- let mut za = ca.clone();
- let mut zb = cb.clone();
+ let c = z.clone();
- let mut za_prev = Float::with_val(PRECISION, Special::Nan);
- let mut zb_prev = Float::with_val(PRECISION, Special::Nan);
+ let mut z_prev = Complex {
+ real: Float::with_val(PRECISION, Special::Nan),
+ imag: Float::with_val(PRECISION, Special::Nan),
+ };
let mut iter_count: u32 = 0x1;
- let mut square_dist = Float::with_val(PRECISION, Special::Nan);
+ let mut square_dist = Float::with_val(PRECISION, Special::Nan);
while {
- square_dist.assign(&za * &za + &zb * &zb);
+ square_dist.assign(&z.real * &z.real + &z.imag * &z.imag);
// Having a larger escape radius gives better
// results with regard to smoothing.
// Check if the value is periodic, i.e. its
// sequence repeats.
- let periodic = za == za_prev && zb == zb_prev;
+ let periodic = z.real == z_prev.real && z.imag == z_prev.imag;
if periodic { iter_count = max_iter_count }
square_dist <= BAILOUT && iter_count < max_iter_count
} {
- za_prev.assign(&za);
- zb_prev.assign(&zb);
+ z_prev.assign(&z);
+
+ iterator(&mut z, &c);
- iterator(&mut za, &mut zb, &ca, &cb);
+ z.real += &extra.real;
+ z.imag -= &extra.imag;
iter_count += 0x1;
}
diff --git a/source/benoit/benoit/rendering.rs b/source/benoit/benoit/renderer.rs
index 923c01f..6b9aaca 100644
--- a/source/benoit/benoit/rendering.rs
+++ b/source/benoit/benoit/renderer.rs
@@ -26,17 +26,20 @@ use crate::benoit::render::render_point;
use std::mem::transmute;
+pub mod colour;
+pub mod render;
+
#[derive(Clone, Copy)]
-pub enum Rendering {
+pub enum Renderer {
Julia,
Normal,
}
-impl Rendering {
- pub fn get_point_renderer(self) -> PointRenderer {
+impl Renderer {
+ pub fn point_renderer(self) -> PointRenderer {
return match self {
- Rendering::Julia => render_point::julia,
- Rendering::Normal => render_point::normal,
+ Renderer::Julia => render_point::julia,
+ Renderer::Normal => render_point::normal,
};
}
diff --git a/source/benoit/benoit/renderer/colour.rs b/source/benoit/benoit/renderer/colour.rs
new file mode 100644
index 0000000..d4e8e74
--- /dev/null
+++ b/source/benoit/benoit/renderer/colour.rs
@@ -0,0 +1,86 @@
+/*
+ Copyright 2021, 2023 Gabriel Bjørnager Jensen.
+
+ This file is part of Benoit.
+
+ Benoit is free software: you can redistribute it
+ and/or modify it under the terms of the GNU
+ Affero General Public License as published by
+ the Free Software Foundation, either version 3
+ of the License, or (at your option) any later
+ version.
+
+ Benoit is distributed in the hope that it will
+ be useful, but WITHOUT ANY WARRANTY; without
+ even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Affero General Public License for more details.
+
+ You should have received a copy of the GNU
+ Affero General Public License along with Benoit.
+ If not, see <https://www.gnu.org/licenses/>.
+*/
+
+use crate::benoit::palette::{Palette, PALETTE_DATA_LENGTH};
+use crate::benoit::render::colour_data::ColourData;
+use crate::benoit::renderer::Renderer;
+
+extern crate rayon;
+
+use rayon::prelude::*;
+
+impl Renderer {
+ pub fn colour(
+ self,
+ buffer: &mut [u8],
+ palette: Palette,
+ canvas_width: u32,
+ canvas_height: u32,
+ multibrot_exponent: f32,
+ max_iter_count: u32,
+ colour_range: f32,
+ iter_count_buffer: &[u32],
+ square_dist_buffer: &[f32],
+ ) {
+ let data = ColourData::new(buffer, canvas_width, canvas_height, multibrot_exponent, max_iter_count, colour_range, palette, iter_count_buffer, square_dist_buffer);
+
+ let (canvas_size, overflow) = canvas_height.overflowing_mul(canvas_width);
+ if overflow { panic!("overflow when calculating canvas size") };
+
+ (0x0..canvas_size).into_par_iter().for_each(|index| {
+ colour_point(&data, index as usize);
+ });
+ }
+}
+
+fn colour_point(data: &ColourData, index: usize) {
+ let (iter_count_buffer, square_dist_buffer) = data.input_buffers();
+
+ let image = data.output_buffers();
+
+ let (exponent, max_iter_count, colour_range, palette_data) = data.consts();
+
+ let iter_count = unsafe { *iter_count_buffer.get_unchecked( index) };
+ let distance = unsafe { *square_dist_buffer.get_unchecked(index) }.sqrt();
+
+ let (red, green, blue) = if iter_count < max_iter_count {
+ let factor = (iter_count as f32 + 1.0 - distance.log(exponent).log(exponent)) / colour_range % 1.0;
+
+ let index = (factor * PALETTE_DATA_LENGTH as f32).round() as usize;
+ unsafe { *palette_data.get_unchecked(index) }
+ } else {
+ (0.0, 0.0, 0.0)
+ };
+
+ let red = (red * 255.0).round() as u8;
+ let green = (green * 255.0).round() as u8;
+ let blue = (blue * 255.0).round() as u8;
+
+ unsafe {
+ let index = index * 0x3;
+
+ *image.get_unchecked_mut(index) = red;
+ *image.get_unchecked_mut(index + 0x1) = green;
+ *image.get_unchecked_mut(index + 0x2) = blue;
+ }
+}
diff --git a/source/benoit/benoit/render/render.rs b/source/benoit/benoit/renderer/render.rs
index 1ff0931..9ec5e73 100644
--- a/source/benoit/benoit/render/render.rs
+++ b/source/benoit/benoit/renderer/render.rs
@@ -21,8 +21,11 @@
If not, see <https://www.gnu.org/licenses/>.
*/
+use crate::benoit::complex::Complex;
+use crate::benoit::fractal::Fractal;
use crate::benoit::render::{IteratorFunction, PointRenderer};
use crate::benoit::render::render_data::RenderData;
+use crate::benoit::renderer::Renderer;
extern crate rayon;
extern crate rug;
@@ -30,24 +33,50 @@ extern crate rug;
use rayon::prelude::*;
use rug::Float;
-pub fn render(iter_count_buffer: &mut [u32], square_dist_buffer: &mut [f32], canvas_width: u32, canvas_height: u32, centre_real: &Float, centre_imag: &Float, zoom: &Float, max_iter_count: u32, inverse: bool, point_renderer: PointRenderer, iterator: IteratorFunction) {
- let data = RenderData::new(iter_count_buffer, square_dist_buffer, canvas_width, canvas_height, centre_real.clone(), centre_imag.clone(), zoom.clone(), max_iter_count, inverse);
+impl Renderer {
+ pub fn render(
+ self,
+ iter_count_buffer: &mut [u32],
+ square_dist_buffer: &mut [f32],
+ fractal: &Fractal,
+ canvas_width: u32,
+ canvas_height: u32,
+ centre: &Complex,
+ zoom: &Float,
+ extra: &Complex,
+ max_iter_count: u32,
+ ) {
+ let data = RenderData::new(
+ iter_count_buffer,
+ square_dist_buffer,
+ canvas_width,
+ canvas_height,
+ centre.clone(),
+ extra.clone(),
+ zoom.clone(),
+ max_iter_count,
+ fractal.inverse(),
+ );
- let (canvas_size, overflow) = canvas_height.overflowing_mul(canvas_width);
- if overflow { panic!("overflow when calculating canvas size") };
+ let (canvas_size, overflow) = canvas_height.overflowing_mul(canvas_width);
+ if overflow { panic!("overflow when calculating canvas size") };
- (0x0..canvas_size).into_par_iter().for_each(|index| {
- render_point(&data, index as u32, point_renderer, iterator);
- });
+ let point_renderer = self.point_renderer();
+ let iterator = fractal.iterator();
+
+ (0x0..canvas_size).into_par_iter().for_each(|index| {
+ render_point(&data, index as usize, point_renderer, iterator);
+ });
+ }
}
-fn render_point(data: &RenderData, index: u32, point_renderer: PointRenderer, iterator: IteratorFunction) {
+fn render_point(data: &RenderData, index: usize, point_renderer: PointRenderer, iterator: IteratorFunction) {
let (iter_count_buffer, square_dist_buffer) = data.output_buffers();
let (canvas_width, _) = data.canvas_size();
- let x = index % canvas_width;
- let y = index / canvas_width;
+ let x = (index % canvas_width as usize) as u32;
+ let y = (index / canvas_width as usize) as u32;
let (iter_count, square_dist) = point_renderer(&data, x, y, iterator);
diff --git a/source/benoit/benoit/video/draw_textual_feedback.rs b/source/benoit/benoit/video/draw_textual_feedback.rs
index cfa2307..72c9d2a 100644
--- a/source/benoit/benoit/video/draw_textual_feedback.rs
+++ b/source/benoit/benoit/video/draw_textual_feedback.rs
@@ -21,6 +21,7 @@
If not, see <https://www.gnu.org/licenses/>.
*/
+use crate::benoit::complex::Complex;
use crate::benoit::video::Video;
extern crate rug;
@@ -32,9 +33,9 @@ use sdl2::rect::Rect;
use sdl2::render::WindowCanvas;
impl Video {
- pub fn draw_textual_feedback(&mut self, real: &Float, imag: &Float, zoom: &Float, max_iter_count: u32) {
- let real_text = format!("REAL: {:.18}", real.to_f64());
- let imag_text = format!("IMAG: {:.18}", imag.to_f64());
+ pub fn draw_textual_feedback(&mut self, centre: &Complex, zoom: &Float, max_iter_count: u32) {
+ let real_text = format!("REAL: {:.18}", centre.real.to_f64());
+ let imag_text = format!("IMAG: {:.18}", centre.imag.to_f64());
let zoom_text = format!("ZOOM: 2^{:.9}", zoom.to_f64().log2());
let iter_text = format!("ITER: {}", max_iter_count);
diff --git a/source/benoit/benoit/video/draw_translation_feedback.rs b/source/benoit/benoit/video/draw_translation_feedback.rs
index aeb7463..05ac70e 100644
--- a/source/benoit/benoit/video/draw_translation_feedback.rs
+++ b/source/benoit/benoit/video/draw_translation_feedback.rs
@@ -21,7 +21,8 @@
If not, see <https://www.gnu.org/licenses/>.
*/
-use crate::benoit::{FeedbackInfo, PRECISION, width_height_ratio};
+use crate::benoit::{PRECISION, width_height_ratio};
+use crate::benoit::complex::Complex;
use crate::benoit::video::Video;
extern crate rug;
@@ -32,7 +33,7 @@ use sdl2::pixels::Color;
use sdl2::rect::Rect;
impl Video {
- pub fn draw_translation_feedback(&mut self, canvas_width: u32, canvas_height: u32, scale: u32, feedback_info: &FeedbackInfo) {
+ pub fn draw_translation_feedback(&mut self, canvas_width: u32, canvas_height: u32, scale: u32, prev_centre: &Complex, prev_zoom: &Float, next_centre: &Complex, next_zoom: &Float) {
let (width_ratio, height_ratio) = width_height_ratio(canvas_width, canvas_height);
let canvas_width = Float::with_val(PRECISION, canvas_width * scale);
@@ -40,7 +41,7 @@ impl Video {
let viewport = {
let (offset_x, offset_y, width, height) = {
- let zoom_ratio = Float::with_val(PRECISION, feedback_info.next_zoom / feedback_info.prev_zoom);
+ let zoom_ratio = Float::with_val(PRECISION, next_zoom / prev_zoom);
let mut width = Float::with_val(PRECISION, 1.0 / &zoom_ratio);
let mut height = Float::with_val(PRECISION, 1.0 / &zoom_ratio);
@@ -49,17 +50,17 @@ impl Video {
// inverted vertical axis compared to those of
// SDL's coordinate system.
- let mut offset_x = feedback_info.next_centre_real.clone();
- let mut offset_y = Float::with_val(PRECISION, -feedback_info.next_centre_imag);
+ let mut offset_x = next_centre.real.clone();
+ let mut offset_y = Float::with_val(PRECISION, -&next_centre.imag);
- offset_x -= feedback_info.prev_centre_real;
- offset_y += feedback_info.prev_centre_imag;
+ offset_x -= &prev_centre.real;
+ offset_y += &prev_centre.imag;
offset_x /= 4.0;
offset_y /= 4.0;
- offset_x *= feedback_info.prev_zoom;
- offset_y *= feedback_info.prev_zoom;
+ offset_x *= prev_zoom;
+ offset_y *= prev_zoom;
offset_x /= width_ratio;
offset_y /= height_ratio;
diff --git a/source/benoit/benoit/video/initialise.rs b/source/benoit/benoit/video/initialise.rs
index 8719b4e..b06d774 100644
--- a/source/benoit/benoit/video/initialise.rs
+++ b/source/benoit/benoit/video/initialise.rs
@@ -37,7 +37,6 @@ impl Video {
let window_title = format!("BENO\u{CE}T {:X}.{:X}.{:X}", VERSION[0x0], VERSION[0x1], VERSION[0x2]);
let mut window_builder = sdl_video.window(window_title.as_str(), canvas_width * scale, canvas_height * scale);
- window_builder.borderless();
window_builder.position_centered();
let window = window_builder.build().expect("unable to open window");