diff options
46 files changed, 1144 insertions, 795 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index 30a4fc3..0c77393 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,11 @@ +# 2.4.0 + +* Clean up and restructure code +* Add multibrot d=4 fractal +* Update messages +* Improve safety +* Add image and render types + # 2.3.0 * Bump minor version @@ -1,6 +1,6 @@ [package] name = "benoit" -version = "2.3.0" +version = "2.4.0" authors = ["Gabriel Bjørnager Jensen"] edition = "2021" description = "Mandelbrot renderer." diff --git a/source/benoit/benoit.rs b/source/benoit/benoit.rs index 715a311..86fa5d0 100644 --- a/source/benoit/benoit.rs +++ b/source/benoit/benoit.rs @@ -22,17 +22,21 @@ */ pub mod app; +pub mod colour_data; pub mod complex; pub mod configuration; +pub mod image; pub mod fractal; pub mod palette; -pub mod renderer; pub mod render; +pub mod render_data; +pub mod renderer; +pub mod script; pub mod video; pub const VERSION: [u32; 0x3] = [ 0x2, // Major - 0x3, // Minor + 0x4, // Minor 0x0, // Patch ]; @@ -40,12 +44,6 @@ pub const PRECISION: u32 = 0x80; pub const BAILOUT: f32 = 256.0; -#[derive(Clone, Copy)] -pub enum ImageFormat { - Png, - Webp, -} - 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 a281304..3eb54d0 100644 --- a/source/benoit/benoit/app.rs +++ b/source/benoit/benoit/app.rs @@ -21,38 +21,29 @@ If not, see <https://www.gnu.org/licenses/>. */ -use crate::benoit::ImageFormat; use crate::benoit::complex::Complex; use crate::benoit::fractal::Fractal; use crate::benoit::palette::Palette; use crate::benoit::renderer::Renderer; -use crate::benoit::video::Video; extern crate rug; use rug::Float; -pub mod allocate_buffers; -pub mod animate; +pub mod configure; pub mod drop; -pub mod dump; pub mod handle_keys; -pub mod initialise; -pub mod interactive; pub mod poll_events; pub mod run; pub struct App { - thread_count: u32, - - fractal: Fractal, + // Configuration: + fractal: Fractal, renderer: Renderer, canvas_width: u32, canvas_height: u32, scale: u32, - frame_start: u32, - frame_stop: u32, centre: Complex, extra: Complex, @@ -63,12 +54,7 @@ pub struct App { palette: Palette, colour_range: f32, - dump_path: String, - image_format: ImageFormat, - - interactive: bool, + // Flags: do_render: bool, do_textual_feedback: bool, - - video: Option<Video>, } diff --git a/source/benoit/benoit/app/animate.rs b/source/benoit/benoit/app/animate.rs deleted file mode 100644 index 157c213..0000000 --- a/source/benoit/benoit/app/animate.rs +++ /dev/null @@ -1,206 +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::{ImageFormat, PRECISION}; -use crate::benoit::app::App; -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; - -use rug::Float; -use rug::ops::PowAssign; -use std::time::Instant; - -impl App { - pub fn animate(&self) -> i32 { - 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); - - // zoom_start: - let mut zoom = Float::with_val(PRECISION, 1.0 / 4.0); - - let zoom_stop = Float::with_val(PRECISION, &self.zoom); - - let zoom_factor = get_zoom_factor(&zoom, &zoom_stop, self.frame_stop); - - zoom = if self.frame_start > 0x0 { - let mut zoom = zoom_factor.clone(); - zoom.pow_assign(frame_count); - - zoom - } else { - zoom - }; - - 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, - ); - - zoom *= &zoom_factor; - } - - return 0x0; - } - - fn still(&self) -> i32 { - let (mut iter_count_buffer, mut square_dist_buffer, mut image) = App::allocate_buffers(self.canvas_width, self.canvas_height); - - 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/configure.rs b/source/benoit/benoit/app/configure.rs new file mode 100644 index 0000000..64db009 --- /dev/null +++ b/source/benoit/benoit/app/configure.rs @@ -0,0 +1,53 @@ +/* + 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::complex::Complex; +use crate::benoit::configuration::Configuration; + +impl App { + #[must_use] + pub fn configure(configuration: Configuration) -> App { + return App { + fractal: configuration.fractal, + renderer: configuration.renderer, + + canvas_width: configuration.canvas_width, + canvas_height: configuration.canvas_height, + scale: configuration.scale, + + centre: Complex::new(configuration.centre_real, configuration.centre_imag), + zoom: configuration.zoom, + + extra: Complex::new(configuration.extra_real, configuration.extra_imag), + + max_iter_count: configuration.max_iter_count, + + palette: configuration.palette, + colour_range: configuration.colour_range, + + do_render: true, + do_textual_feedback: false, + }; + } +} diff --git a/source/benoit/benoit/app/dump.rs b/source/benoit/benoit/app/dump.rs deleted file mode 100644 index fe92558..0000000 --- a/source/benoit/benoit/app/dump.rs +++ /dev/null @@ -1,66 +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::ImageFormat; -use crate::benoit::app::App; - -extern crate png; -extern crate webp; - -use std::fs::{File, write}; -use std::io::BufWriter; - -impl App { - 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), - } - } -} - -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); - - let mut encoder = png::Encoder::new(file_buffer, canvas_width, canvas_height); - encoder.set_color(png::ColorType::Rgb); - encoder.set_depth(png::BitDepth::Eight); - encoder.set_compression(png::Compression::Fast); - encoder.set_srgb(png::SrgbRenderingIntent::Perceptual); - - let mut writer = encoder.write_header().expect("unable to write image"); - writer.write_image_data(image).expect("unable to write image"); -} - -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(); - - write(path, &*data).expect("unable to write image"); -} diff --git a/source/benoit/benoit/app/handle_keys.rs b/source/benoit/benoit/app/handle_keys.rs index 73d47f7..b0fdcb9 100644 --- a/source/benoit/benoit/app/handle_keys.rs +++ b/source/benoit/benoit/app/handle_keys.rs @@ -36,7 +36,7 @@ pub const MIN_COLOUR_RANGE: f32 = 2.0; impl App { #[must_use] - pub fn handle_keys(&mut self, scan_code: Scancode, state: KeyboardState) -> bool { + pub(super) 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) }; @@ -49,7 +49,7 @@ impl App { Scancode::Left => self.cycle_palette(-0x1), Scancode::RAlt => self.cycle_fractal(0x1), Scancode::Right => self.cycle_palette(0x1), - Scancode::Tab => self.cycle_rendering(), + Scancode::Tab => self.toggle_julia(), Scancode::X => self.reset_viewport(), Scancode::Z => self.dump_info(), _ => {}, @@ -123,15 +123,13 @@ impl App { eprintln!("renderer the {}", self.fractal.kind().name()); } - fn cycle_rendering(&mut self) { - let renderer = self.renderer.cycle(); + fn toggle_julia(&mut self) { + self.renderer.toggle(); - match renderer { + match self.renderer { Renderer::Julia => eprintln!("enabled the julia set"), Renderer::Normal => eprintln!("disabled the julia set"), }; - - self.renderer = renderer; } fn toggle_inverse(&mut self) { @@ -167,7 +165,7 @@ impl App { } fn dump_info(&self) { - eprintln!("info dump:"); + eprintln!("info dump: the {}", self.fractal.kind().name()); 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 deleted file mode 100644 index 2876e97..0000000 --- a/source/benoit/benoit/app/initialise.rs +++ /dev/null @@ -1,95 +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::complex::Complex; -use crate::benoit::configuration::Configuration; -use crate::benoit::video::Video; - -extern crate rayon; - -use rayon::ThreadPoolBuilder; -use std::env::args; -use std::thread::available_parallelism; - -impl App { - #[must_use] - pub fn initialise() -> App { - let mut arguments = args(); - - let configuration = match arguments.nth(0x1) { - Some(path) => Configuration::load(path.as_str()), - None => Configuration::default(), - }; - - let thread_count: u32 = if configuration.thread_count == 0x0 { - match available_parallelism() { - Ok(ammount) => ammount.get() as u32, - _ => 0x2, - } - } else { - configuration.thread_count - }; - - eprintln!("using {thread_count} threads"); - - let video = match configuration.interactive { - true => Some(Video::initialise(configuration.canvas_width, configuration.canvas_height, configuration.scale)), - false => None, - }; - - ThreadPoolBuilder::new().num_threads(configuration.thread_count as usize).build_global().unwrap(); - - return App { - thread_count: thread_count, - - fractal: configuration.fractal, - renderer: configuration.renderer, - - canvas_width: configuration.canvas_width, - canvas_height: configuration.canvas_height, - scale: configuration.scale, - frame_start: configuration.frame_start, - frame_stop: configuration.frame_stop, - - centre: Complex { real: configuration.centre_real, imag: configuration.centre_imag }, - zoom: configuration.zoom, - - extra: Complex { real: configuration.extra_real, imag: configuration.extra_imag }, - - max_iter_count: configuration.max_iter_count, - - palette: configuration.palette, - colour_range: configuration.colour_range, - - dump_path: configuration.dump_path, - image_format: configuration.image_format, - - interactive: configuration.interactive, - do_render: true, - do_textual_feedback: false, - - video: video, - }; - } -} diff --git a/source/benoit/benoit/app/interactive.rs b/source/benoit/benoit/app/interactive.rs deleted file mode 100644 index 94db797..0000000 --- a/source/benoit/benoit/app/interactive.rs +++ /dev/null @@ -1,168 +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::complex::Complex; -use crate::benoit::video::Video; - -extern crate rug; - -use rug::{Assign, Float}; -use std::time::Instant; - -impl App { - #[must_use] - pub fn interactive(&mut self) -> i32 { - assert_eq!(self.interactive, true); - let mut video = self.video.take().unwrap(); - - App::print_controls(); - - let mut event_pump = video.sdl.event_pump().expect("unable to get event pump"); - - let (mut iter_count_buffer, mut square_dist_buffer, mut image) = App::allocate_buffers(self.canvas_width, self.canvas_height); - - // Used for colouring: - let mut prev_max_iter_count = self.max_iter_count; - let mut prev_multibrot_exponent = self.fractal.exponent(); - - // Used for translation feedback: - let mut prev_centre = self.centre.clone(); - let mut prev_zoom = self.zoom.clone(); - - loop { - let frame_start = Instant::now(); - - if self.poll_events(&mut event_pump) { break } - - if self.do_render { - self.render(&mut iter_count_buffer[..], &mut square_dist_buffer[..]); - - prev_max_iter_count = self.max_iter_count; - prev_multibrot_exponent = self.fractal.exponent(); - - prev_centre.assign(&self.centre); - prev_zoom.assign( &self.zoom); - - self.do_render = false; - }; - - 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, &prev_zoom); - - video.update(); - - video.sync(&frame_start); - } - - return 0x0; - } - - 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. - - &self.centre.real != &prev_centre.real - || &self.centre.imag != &prev_centre.imag - || &self.zoom != prev_zoom - }{ - 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, &self.zoom, self.max_iter_count) }; - } - - pub fn print_controls() { - println!("Controls:"); - 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"); - println!(); - 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!(); - 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 af200ec..df24b0e 100644 --- a/source/benoit/benoit/app/poll_events.rs +++ b/source/benoit/benoit/app/poll_events.rs @@ -30,7 +30,7 @@ use sdl2::event::Event; impl App { #[must_use] - pub fn poll_events(&mut self, pump: &mut EventPump) -> bool { + pub(super) fn poll_events(&mut self, pump: &mut EventPump) -> bool { loop { let event = match pump.poll_event() { Some(event) => event, diff --git a/source/benoit/benoit/app/run.rs b/source/benoit/benoit/app/run.rs index 8d6ee0d..cefe5f7 100644 --- a/source/benoit/benoit/app/run.rs +++ b/source/benoit/benoit/app/run.rs @@ -21,24 +21,130 @@ If not, see <https://www.gnu.org/licenses/>. */ -use crate::benoit::VERSION; use crate::benoit::app::App; +use crate::benoit::complex::Complex; +use crate::benoit::image::Image; +use crate::benoit::render::Render; +use crate::benoit::video::Video; -extern crate sdl2; +extern crate rug; + +use rug::{Assign, Float}; +use std::time::Instant; impl App { #[must_use] - pub fn run(&mut self) -> i32 { + pub fn run(mut self) -> i32 { + let mut video = Video::initialise(self.canvas_width, self.canvas_height, self.scale); + + App::print_controls(); + + let mut event_pump = video.sdl.event_pump().expect("unable to get event pump"); + + let mut image = Image::allocate( self.canvas_width, self.canvas_height); + let mut render = Render::allocate(self.canvas_width, self.canvas_height); + + // Used for translation feedback: + let mut prev_centre = self.centre.clone(); + let mut prev_zoom = self.zoom.clone(); + + loop { + let frame_start = Instant::now(); + + if self.poll_events(&mut event_pump) { break } + + if self.do_render { + self.render(&mut render); + + prev_centre.assign(&self.centre); + prev_zoom.assign( &self.zoom); + + self.do_render = false; + }; + + image.colour(&render, self.palette, self.max_iter_count, self.colour_range); + + video.draw(&image, self.scale); + self.draw_feedback(&mut video, &prev_centre, &prev_zoom); + + video.update(); + + video.sync(&frame_start); + } + + return 0x0; + } + + fn render(&self, render: &mut Render) { + eprint!("rendering..."); + + let time_start = Instant::now(); + + render.render( + self.fractal, + self.renderer, + &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); + } + + 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. + + &self.centre.real != &prev_centre.real + || &self.centre.imag != &prev_centre.imag + || &self.zoom != prev_zoom + }{ + 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, &self.zoom, self.max_iter_count) }; + } + + fn print_controls() { + println!("Controls:"); + 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}[1mBENO\u{CE}T\u{1B}[0m {:X}.{:X}.{:X}", VERSION[0x0], VERSION[0x1], VERSION[0x2]); - println!("Copyright 2021, 2023 Gabriel Bj\u{F8}rnager Jensen."); + println!("- \u{1B}[1mQ\u{1B}[0m Zoom out"); + println!("- \u{1B}[1mE\u{1B}[0m Zoom in"); println!(); - println!("COCITAVIT\u{B7}ERCO\u{B7}FVIT"); + 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!(); + 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!(); - - return match self.interactive { - true => self.interactive(), - false => self.animate(), - }; } -} +}
\ No newline at end of file diff --git a/source/benoit/benoit/render/colour_data.rs b/source/benoit/benoit/colour_data.rs index 114a8b0..6e994fb 100644 --- a/source/benoit/benoit/render/colour_data.rs +++ b/source/benoit/benoit/colour_data.rs @@ -21,7 +21,8 @@ If not, see <https://www.gnu.org/licenses/>. */ -use crate::benoit::palette::{Palette, PaletteData}; +use crate::benoit::image::Image; +use crate::benoit::palette::PaletteData; use std::slice::{from_raw_parts, from_raw_parts_mut}; @@ -37,12 +38,22 @@ pub struct ColourData { iter_count_buffer: *const u32, square_dist_buffer: *const f32, - image: *mut u8, + image: *mut (u8, u8, u8), } 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 { + pub fn new( + image: &mut Image, + canvas_width: u32, + canvas_height: u32, + exponent: f32, + max_iter_count: u32, + colour_range: f32, + palette_data: &'static PaletteData, + iter_count_buffer: &[u32], + square_dist_buffer: &[f32], + ) -> ColourData { return ColourData { canvas_size: canvas_height as usize * canvas_width as usize, @@ -50,12 +61,12 @@ impl ColourData { max_iter_count: max_iter_count, colour_range: colour_range, - palette_data: palette.get_data(), + palette_data: palette_data, iter_count_buffer: iter_count_buffer.as_ptr(), square_dist_buffer: square_dist_buffer.as_ptr(), - image: image.as_mut_ptr(), + image: image.mut_data().as_mut_ptr(), }; } @@ -68,10 +79,8 @@ impl ColourData { } #[must_use] - pub fn output_buffers(&self) -> &mut [u8] { - let image = unsafe { from_raw_parts_mut(self.image, self.canvas_size * 0x3) }; - - return image; + pub unsafe fn image<'a>(&'a self) -> &'a mut [(u8, u8, u8)] { + return from_raw_parts_mut(self.image, self.canvas_size); } #[must_use] diff --git a/source/benoit/benoit/complex.rs b/source/benoit/benoit/complex.rs index 146bd18..204b586 100644 --- a/source/benoit/benoit/complex.rs +++ b/source/benoit/benoit/complex.rs @@ -25,16 +25,18 @@ extern crate rug; use rug::{Assign, Float}; +#[derive(Clone)] pub struct Complex { pub real: Float, pub imag: Float, } impl Complex { - pub fn clone(&self) -> Self { + #[must_use] + pub fn new(real: Float, imag: Float) -> Complex { return Complex { - real: self.real.clone(), - imag: self.imag.clone(), + real: real, + imag: imag, }; } diff --git a/source/benoit/benoit/configuration.rs b/source/benoit/benoit/configuration.rs index 7e0c97a..2975430 100644 --- a/source/benoit/benoit/configuration.rs +++ b/source/benoit/benoit/configuration.rs @@ -21,8 +21,9 @@ If not, see <https://www.gnu.org/licenses/>. */ -use crate::benoit::{fractal, ImageFormat, PRECISION}; -use crate::benoit::fractal::Fractal; +use crate::benoit::{PRECISION}; +use crate::benoit::fractal::{Fractal, FractalKind}; +use crate::benoit::image::ImageFormat; use crate::benoit::palette::Palette; use crate::benoit::renderer::Renderer; @@ -58,17 +59,14 @@ pub struct Configuration { pub dump_path: String, pub image_format: ImageFormat, - - pub interactive: bool, } 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_ZOOM: f64 = 1.0; pub const DEFAULT_MAX_ITER_COUNT: u32 = 0x100; pub const DEFAULT_COLOUR_RANGE: f32 = 64.0; @@ -78,7 +76,7 @@ impl Configuration { return Configuration { thread_count: 0x0, - fractal: Fractal::new(fractal::Kind::Mandelbrot, false), + fractal: Fractal::new(FractalKind::Multibrot3, false), renderer: Renderer::Normal, canvas_width: 0x100, @@ -101,8 +99,6 @@ impl Configuration { dump_path: "./render".to_string(), image_format: ImageFormat::Png, - - interactive: true, }; } } diff --git a/source/benoit/benoit/configuration/load.rs b/source/benoit/benoit/configuration/load.rs index 3f5ec11..6229e58 100644 --- a/source/benoit/benoit/configuration/load.rs +++ b/source/benoit/benoit/configuration/load.rs @@ -21,8 +21,10 @@ If not, see <https://www.gnu.org/licenses/>. */ -use crate::benoit::{fractal, ImageFormat, PRECISION}; +use crate::benoit::PRECISION; use crate::benoit::configuration::Configuration; +use crate::benoit::fractal::FractalKind; +use crate::benoit::image::ImageFormat; use crate::benoit::palette::Palette; use crate::benoit::renderer::Renderer; @@ -41,8 +43,6 @@ impl Configuration { 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"), @@ -62,10 +62,11 @@ impl Configuration { 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, + "burningship" => FractalKind::BurningShip, + "mandelbrot" => FractalKind::Mandelbrot, + "multibrot3" => FractalKind::Multibrot3, + "multibrot4" => FractalKind::Multibrot4, + "tricorn" => FractalKind::Tricorn, name => panic!("invalid fractal kind \"{name}\""), }); } diff --git a/source/benoit/benoit/fractal.rs b/source/benoit/benoit/fractal.rs index 7ca6035..1496450 100644 --- a/source/benoit/benoit/fractal.rs +++ b/source/benoit/benoit/fractal.rs @@ -21,27 +21,33 @@ If not, see <https://www.gnu.org/licenses/>. */ -use crate::benoit::render::IteratorFunction; -use crate::benoit::render::iterate; +use crate::benoit::complex::Complex; use std::mem::transmute; +mod iterate; + +#[derive(Clone, Copy)] pub struct Fractal { - kind: Kind, + kind: FractalKind, inverse: bool, } #[derive(Clone, Copy)] #[repr(u8)] -pub enum Kind { +pub enum FractalKind { BurningShip, Mandelbrot, Multibrot3, + Multibrot4, Tricorn, } +pub type IteratorFunction = fn(&mut Complex, &Complex); + impl Fractal { - pub const fn new(kind: Kind, inverse: bool) -> Self { + #[must_use] + pub const fn new(kind: FractalKind, inverse: bool) -> Self { let fractal = Fractal { kind: kind, inverse: inverse, @@ -51,7 +57,7 @@ impl Fractal { } #[must_use] - pub fn kind(&self) -> Kind { + pub fn kind(&self) -> FractalKind { return self.kind; } @@ -63,23 +69,26 @@ impl Fractal { #[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, + FractalKind::BurningShip => 2.0, + FractalKind::Mandelbrot => 2.0, + FractalKind::Multibrot3 => 3.0, + FractalKind::Multibrot4 => 4.0, + FractalKind::Tricorn => 2.0, }; } + #[must_use] 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, + FractalKind::BurningShip => iterate::burning_ship, + FractalKind::Mandelbrot => iterate::mandelbrot, + FractalKind::Multibrot3 => iterate::multibrot3, + FractalKind::Multibrot4 => iterate::multibrot4, + FractalKind::Tricorn => iterate::tricorn, }; } - pub fn set_kind(&mut self, kind: Kind) { + pub fn set_kind(&mut self, kind: FractalKind) { self.kind = kind; } @@ -93,28 +102,29 @@ impl Fractal { let raw = self.kind as i8 + direction; let raw: u8 = if raw < 0x0 { - Kind::MAX - } else if raw > Kind::MAX as i8 { + FractalKind::MAX + } else if raw > FractalKind::MAX as i8 { 0x0 } else { raw as u8 }; - let new: Kind = unsafe { transmute(raw) }; + let new: FractalKind = unsafe { transmute(raw) }; self.kind = new; } } -impl Kind { - const MAX: u8 = Kind::Tricorn as u8; +impl FractalKind { + const MAX: u8 = FractalKind::Tricorn as u8; 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", + FractalKind::BurningShip => "burning ship", + FractalKind::Mandelbrot => "mandelbrot set", + FractalKind::Multibrot3 => "multibrot (d=3) set", + FractalKind::Multibrot4 => "multibrot (d=4) set", + FractalKind::Tricorn => "tricorn", }; } } diff --git a/source/benoit/benoit/render/iterate.rs b/source/benoit/benoit/fractal/iterate.rs index 723da26..59c5a77 100644 --- a/source/benoit/benoit/render/iterate.rs +++ b/source/benoit/benoit/fractal/iterate.rs @@ -24,9 +24,11 @@ pub mod burning_ship; pub mod mandelbrot; pub mod multibrot3; +pub mod multibrot4; pub mod tricorn; pub use burning_ship::*; pub use mandelbrot::*; pub use multibrot3::*; +pub use multibrot4::*; pub use tricorn::*; diff --git a/source/benoit/benoit/render/iterate/burning_ship.rs b/source/benoit/benoit/fractal/iterate/burning_ship.rs index 9e4f029..9e4f029 100644 --- a/source/benoit/benoit/render/iterate/burning_ship.rs +++ b/source/benoit/benoit/fractal/iterate/burning_ship.rs diff --git a/source/benoit/benoit/render/iterate/mandelbrot.rs b/source/benoit/benoit/fractal/iterate/mandelbrot.rs index 078cc18..078cc18 100644 --- a/source/benoit/benoit/render/iterate/mandelbrot.rs +++ b/source/benoit/benoit/fractal/iterate/mandelbrot.rs diff --git a/source/benoit/benoit/render/iterate/multibrot3.rs b/source/benoit/benoit/fractal/iterate/multibrot3.rs index fc6f872..da58a5e 100644 --- a/source/benoit/benoit/render/iterate/multibrot3.rs +++ b/source/benoit/benoit/fractal/iterate/multibrot3.rs @@ -38,8 +38,8 @@ pub fn multibrot3(z: &mut Complex, c: &Complex) { // = a^3+(a^2)bi-ab^2-(b^3)i+2(a^2)bi-2ab^2 // = a^3+3(a^2)bi-3ab^2-(b^3)i // - // <=> z_a = a^3-3ab^2 - // z_b = 3(a^2)b-b^3 + // <=> a = a^3-3ab^2 + // b = 3(a^2)b-b^3 let mut temporary0 = Float::with_val(PRECISION, &z.imag * &z.imag); // b^2 @@ -59,4 +59,5 @@ pub fn multibrot3(z: &mut Complex, c: &Complex) { 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/fractal/iterate/multibrot4.rs b/source/benoit/benoit/fractal/iterate/multibrot4.rs new file mode 100644 index 0000000..d040cf3 --- /dev/null +++ b/source/benoit/benoit/fractal/iterate/multibrot4.rs @@ -0,0 +1,64 @@ +/* + 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::complex::Complex; + +use crate::benoit::PRECISION; + +extern crate rug; + +use rug::{Assign, Float}; + +pub fn multibrot4(z: &mut Complex, c: &Complex) { + // (a+bi)^4 + // = (a+bi)^2*(a+bi)^2 + // = (a^2-b^2+2abi)^2 + // = (a^2-b^2+2abi)(a^2-b^2+2abi) + // = a^4-(a^2)b^2+2(a^3)bi-(a^2)b^2+b^4-2a(b^3)i-4(a^2)b^2+2(a^3)bi-2a(b^3)i-4(a^2)b^2 + // = a^4-6(a^2)b^2+4(a^3)bi+b^4-4a(b^3)i + // + // <=> a = a^4-6(a^2)b^2+b^4 + // b = 4(a^3)bi-4a(b^3)i + + let temporary0 = Float::with_val(PRECISION, &z.real * &z.real); // a^2 + let temporary1 = Float::with_val(PRECISION, &z.imag * &z.imag); // b^2 + + let mut temporary2 = Float::with_val(PRECISION, &z.real * &z.imag); // ab + temporary2 *= 4.0; // 4ab + + z.real.assign(&temporary0); // a^2 + z.real /= 6.0; // a^2/6 + z.real -= &temporary1; // a^2/6-b^2 + z.real *= &temporary0; // a^4/6-(a^2)b^2 + z.real *= 6.0; // a^4-6(a^2)b^2 + + z.imag.assign(&temporary1); // b^2 + z.imag *= -1.0; // -b^2 + z.imag += &temporary0; // a^2-b^2 + z.imag *= temporary2; // 4(a^3)b-4ab^3 + + z.real += temporary1.square(); // a^4-6(a^2)b^2+b^4 + z.real += &c.real; // a^4-6(a^2)b^2+b^4+Re(c) + + z.imag += &c.imag; // 4(a^3)b-4ab^3+Im(c) +} diff --git a/source/benoit/benoit/render/iterate/tricorn.rs b/source/benoit/benoit/fractal/iterate/tricorn.rs index b562d66..a64b3aa 100644 --- a/source/benoit/benoit/render/iterate/tricorn.rs +++ b/source/benoit/benoit/fractal/iterate/tricorn.rs @@ -36,13 +36,13 @@ pub fn tricorn(z: &mut Complex, c: &Complex) { z.real -= &z.imag * &z.imag; // a^2-b^2 z.real += &c.real; // a^2 - z.imag *= &za_temporary; + z.imag *= &za_temporary; // ab // 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. - z.imag *= -2.0; - z.imag += &c.imag; + z.imag *= -2.0; // -2ab + z.imag += &c.imag; // -2ab+Im(c) } diff --git a/source/benoit/benoit/image.rs b/source/benoit/benoit/image.rs new file mode 100644 index 0000000..a140a01 --- /dev/null +++ b/source/benoit/benoit/image.rs @@ -0,0 +1,78 @@ +/* + 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 std::ops::{Index, IndexMut}; +use std::slice::from_raw_parts; + +pub mod allocate; +pub mod colour; +pub mod dump; + +pub struct Image { + width: u32, + height: u32, + + data: Vec::<(u8, u8, u8)>, +} + +#[derive(Clone, Copy)] +pub enum ImageFormat { + Png, + Webp, +} + +impl Image { + #[must_use] + pub fn size(&self) -> (u32, u32) { + return (self.width, self.height); + } + + #[must_use] + pub fn mut_data<'a>(&'a mut self) -> &'a mut [(u8, u8, u8)] { + return &mut self.data[..]; + } + + #[must_use] + pub fn raw<'a>(&'a self) -> &'a [u8] { + let data_pointer = self.data.as_ptr() as *const u8; + + let length = self.height as usize * self.width as usize * 0x3; + let slice = unsafe { from_raw_parts(data_pointer, length) }; + + return slice; + } +} + +impl Index<usize> for Image { + type Output = (u8, u8, u8); + + fn index<'a>(&'a self, index: usize) -> &'a Self::Output { + return &self.data[index]; + } +} + +impl IndexMut<usize> for Image { + fn index_mut<'a>(&'a mut self, index: usize) -> &'a mut Self::Output { + return &mut self.data[index]; + } +} diff --git a/source/benoit/benoit/image/allocate.rs b/source/benoit/benoit/image/allocate.rs new file mode 100644 index 0000000..bbdd61b --- /dev/null +++ b/source/benoit/benoit/image/allocate.rs @@ -0,0 +1,41 @@ +/* + 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::image::Image; + +impl Image { + #[must_use] + pub fn allocate(width: u32, height: u32) -> Image { + let (canvas_size, overflow) = (height as usize).overflowing_mul(width as usize); + if overflow { panic!("overflow when calculating canvas size") }; + + let data: Vec::<(u8, u8, u8)> = vec![(0x0, 0x0, 0x0); canvas_size]; + + return Image { + width: width, + height: height, + + data: data, + }; + } +} diff --git a/source/benoit/benoit/renderer/colour.rs b/source/benoit/benoit/image/colour.rs index d4e8e74..18085dd 100644 --- a/source/benoit/benoit/renderer/colour.rs +++ b/source/benoit/benoit/image/colour.rs @@ -21,31 +21,36 @@ If not, see <https://www.gnu.org/licenses/>. */ +use crate::benoit::colour_data::ColourData; +use crate::benoit::image::Image; use crate::benoit::palette::{Palette, PALETTE_DATA_LENGTH}; -use crate::benoit::render::colour_data::ColourData; -use crate::benoit::renderer::Renderer; +use crate::benoit::render::Render; 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") }; +impl Image { + pub fn colour(&mut self, render: &Render, palette: Palette, new_max_iter_count: u32, colour_range: f32) { + if render.canvas_size() != self.size() { panic!("canvas size mismatch") }; + + let (fractal, max_iter_count) = render.info().expect("cannot colour before render"); + + let (iter_count_buffer, square_dist_buffer) = render.data(); + + let data = ColourData::new( + self, + self.width, + self.height, + fractal.exponent(), + max_iter_count.min(new_max_iter_count), + colour_range, + palette.data(), + &iter_count_buffer, + &square_dist_buffer, + ); + + let canvas_size = self.height as usize * self.width as usize; (0x0..canvas_size).into_par_iter().for_each(|index| { colour_point(&data, index as usize); @@ -56,18 +61,18 @@ impl Renderer { fn colour_point(data: &ColourData, index: usize) { let (iter_count_buffer, square_dist_buffer) = data.input_buffers(); - let image = data.output_buffers(); + let image = unsafe { data.image() }; 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 iter_count = iter_count_buffer[ index]; + let distance = square_dist_buffer[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 factor = (iter_count as f32 + 1.0 - distance.ln().log(exponent)) / colour_range; - let index = (factor * PALETTE_DATA_LENGTH as f32).round() as usize; - unsafe { *palette_data.get_unchecked(index) } + let index = (factor * PALETTE_DATA_LENGTH as f32).round() as usize % PALETTE_DATA_LENGTH; + palette_data[index] } else { (0.0, 0.0, 0.0) }; @@ -76,11 +81,5 @@ fn colour_point(data: &ColourData, index: usize) { 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; - } + image[index] = (red, green, blue); } diff --git a/source/benoit/benoit/image/dump.rs b/source/benoit/benoit/image/dump.rs new file mode 100644 index 0000000..18c166c --- /dev/null +++ b/source/benoit/benoit/image/dump.rs @@ -0,0 +1,65 @@ +/* + 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::image::{Image, ImageFormat}; + +extern crate png; +extern crate webp; + +use std::fs::{File, write}; +use std::io::BufWriter; + +impl Image { + pub fn dump(&self, path: &str, format: ImageFormat) { + match format { + ImageFormat::Png => self.dump_png( path), + ImageFormat::Webp => self.dump_webp(path), + } + } + + fn dump_png(&self, path: &str) { + let path = path.to_owned() + ".png"; + + let file = File::create(path).expect("unable to create file"); + let file_buffer = BufWriter::new(file); + + let mut encoder = png::Encoder::new(file_buffer, self.width, self.height); + encoder.set_color(png::ColorType::Rgb); + encoder.set_depth(png::BitDepth::Eight); + encoder.set_compression(png::Compression::Fast); + encoder.set_srgb(png::SrgbRenderingIntent::Perceptual); + + let mut writer = encoder.write_header().expect("unable to write image"); + writer.write_image_data(self.raw()).expect("unable to write image"); + } + + fn dump_webp(&self, path: &str) { + let path = path.to_owned() + ".webp"; + + let encoder = webp::Encoder::from_rgb(self.raw(), self.width, self.height); + + let data = encoder.encode_lossless(); + + write(path, &*data).expect("unable to write image"); + } +} diff --git a/source/benoit/benoit/palette.rs b/source/benoit/benoit/palette.rs index d270682..b697af7 100644 --- a/source/benoit/benoit/palette.rs +++ b/source/benoit/benoit/palette.rs @@ -31,6 +31,7 @@ use std::mem::transmute; mod paint; #[derive(Clone, Copy, Sequence)] +#[repr(u8)] pub enum Palette { Ancient, Fire, @@ -40,40 +41,6 @@ pub enum Palette { Sapphire, } -// We would like to precalculate the palettes at -// compile-time, but Rust does not support -// floating-point arithmetic there. - -pub const PALETTE_DATA_LENGTH: usize = 0x1000; -pub type PaletteData = [(f32, f32, f32); PALETTE_DATA_LENGTH]; - -const fn default_palette_data() -> PaletteData { - return [(0.0, 0.0, 0.0); PALETTE_DATA_LENGTH]; -} - -static mut DATA_ANCIENT: PaletteData = default_palette_data(); -static mut DATA_FIRE: PaletteData = default_palette_data(); -static mut DATA_GREYSCALE: PaletteData = default_palette_data(); -static mut DATA_HSV: PaletteData = default_palette_data(); -static mut DATA_LCH: PaletteData = default_palette_data(); -static mut DATA_SAPPHIRE: PaletteData = default_palette_data(); - -#[ctor] -fn calculate_palettes() { - for palette in all::<Palette>() { - let data = palette.get_data_mut(); - let function = palette.function(); - - for index in 0x0..PALETTE_DATA_LENGTH { - let factor = index as f32 / PALETTE_DATA_LENGTH as f32; - - let (red, green, blue) = function(factor); - - data[index as usize] = (red, green, blue); - } - } -} - impl Palette { const MIN: Self = Palette::Ancient; const MAX: Self = Palette::Sapphire; @@ -91,8 +58,8 @@ impl Palette { } #[must_use] - pub fn get_data(self) -> &'static PaletteData { - return &*self.get_data_mut(); + pub fn data(self) -> &'static PaletteData { + return &*self.mut_data(); } #[must_use] @@ -123,7 +90,7 @@ impl Palette { } #[must_use] - fn get_data_mut(self) -> &'static mut PaletteData { + fn mut_data(self) -> &'static mut PaletteData { return unsafe { match self { Palette::Ancient => &mut DATA_ANCIENT, Palette::Fire => &mut DATA_FIRE, @@ -134,3 +101,37 @@ impl Palette { } }; } } + +// We would like to precalculate the palettes at +// compile-time, but Rust does not support +// floating-point arithmetic there. + +pub const PALETTE_DATA_LENGTH: usize = 0x1000; +pub type PaletteData = [(f32, f32, f32); PALETTE_DATA_LENGTH]; + +const fn default_palette_data() -> PaletteData { + return [(0.0, 0.0, 0.0); PALETTE_DATA_LENGTH]; +} + +static mut DATA_ANCIENT: PaletteData = default_palette_data(); +static mut DATA_FIRE: PaletteData = default_palette_data(); +static mut DATA_GREYSCALE: PaletteData = default_palette_data(); +static mut DATA_HSV: PaletteData = default_palette_data(); +static mut DATA_LCH: PaletteData = default_palette_data(); +static mut DATA_SAPPHIRE: PaletteData = default_palette_data(); + +#[ctor] +fn calculate_palettes() { + for palette in all::<Palette>() { + let data = palette.mut_data(); + let function = palette.function(); + + for index in 0x0..PALETTE_DATA_LENGTH { + let factor = index as f32 / PALETTE_DATA_LENGTH as f32; + + let (red, green, blue) = function(factor); + + data[index as usize] = (red, green, blue); + } + } +} diff --git a/source/benoit/benoit/palette/paint/hsv.rs b/source/benoit/benoit/palette/paint/hsv.rs index 7f04261..a9a6d9e 100644 --- a/source/benoit/benoit/palette/paint/hsv.rs +++ b/source/benoit/benoit/palette/paint/hsv.rs @@ -21,8 +21,6 @@ If not, see <https://www.gnu.org/licenses/>. */ -use std::hint::unreachable_unchecked; - pub fn hsv(factor: f32) -> (f32, f32, f32) { return hsv_to_rgb(factor, 7.0 / 8.0, 7.0 / 8.0); } @@ -49,7 +47,7 @@ fn hsv_to_rgb(hue: f32, saturation: f32, value: f32) -> (f32, f32, f32) { 0x3 => (p, q, v), 0x4 => (t, p, v), 0x5 => (v, p, q), - _ => unsafe { unreachable_unchecked() }, + _ => unreachable!(), } }; } diff --git a/source/benoit/benoit/render.rs b/source/benoit/benoit/render.rs index b81679c..6e2b6ef 100644 --- a/source/benoit/benoit/render.rs +++ b/source/benoit/benoit/render.rs @@ -21,14 +21,34 @@ If not, see <https://www.gnu.org/licenses/>. */ -use crate::benoit::complex::Complex; -use crate::benoit::render::render_data::RenderData; +use crate::benoit::fractal::Fractal; -pub mod colour_data; -pub mod iterate; -pub mod render_data; -pub mod render_point; +pub mod allocate; +pub mod render; -pub type IteratorFunction = fn(&mut Complex, &Complex); +pub struct Render { + canvas_width: u32, + canvas_height: u32, -pub type PointRenderer = fn(&RenderData, u32, u32, IteratorFunction) -> (u32, f32); + info: Option<(Fractal, u32)>, + + iter_count_buffer: Vec::<u32>, + square_dist_buffer: Vec::<f32>, +} + +impl Render { + #[must_use] + pub fn canvas_size(&self) -> (u32, u32) { + return (self.canvas_width, self.canvas_height); + } + + #[must_use] + pub fn info(&self) -> Option<(Fractal, u32)> { + return self.info.clone(); + } + + #[must_use] + pub fn data<'a>(&'a self) -> (&'a Vec::<u32>, &'a Vec::<f32>) { + return (&self.iter_count_buffer, &self.square_dist_buffer); + } +} diff --git a/source/benoit/benoit/app/allocate_buffers.rs b/source/benoit/benoit/render/allocate.rs index 44bbb28..e76f540 100644 --- a/source/benoit/benoit/app/allocate_buffers.rs +++ b/source/benoit/benoit/render/allocate.rs @@ -21,18 +21,28 @@ If not, see <https://www.gnu.org/licenses/>. */ -use crate::benoit::app::App; +use crate::benoit::render::Render; -impl App { - #[must_use] - pub fn allocate_buffers(canvas_width: u32, canvas_height: u32) -> (Vec::<u32>, Vec::<f32>, Vec::<u8>) { - let canvas_size = canvas_height as usize * canvas_width as usize; +impl Render { + pub fn allocate(canvas_width: u32, canvas_height: u32) -> Render { + let canvas_size = { + let (canvas_size, overflow) = canvas_height.overflowing_mul(canvas_width); + if overflow { panic!("overflow when calculating canvas size") }; + + canvas_size as usize + }; let iter_count_buffer: Vec::<u32> = vec![0x0; canvas_size]; let square_dist_buffer: Vec::<f32> = vec![0.0; canvas_size]; - let image: Vec::<u8> = vec![0x0; canvas_size * 0x3]; + return Render { + canvas_width: canvas_width, + canvas_height: canvas_height, + + info: None, - return (iter_count_buffer, square_dist_buffer, image); + iter_count_buffer: iter_count_buffer, + square_dist_buffer: square_dist_buffer, + }; } } diff --git a/source/benoit/benoit/renderer/render.rs b/source/benoit/benoit/render/render.rs index 9ec5e73..731f347 100644 --- a/source/benoit/benoit/renderer/render.rs +++ b/source/benoit/benoit/render/render.rs @@ -22,10 +22,10 @@ */ 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; +use crate::benoit::fractal::{Fractal, IteratorFunction}; +use crate::benoit::render::Render; +use crate::benoit::render_data::RenderData; +use crate::benoit::renderer::{PointRenderer, Renderer}; extern crate rayon; extern crate rug; @@ -33,24 +33,23 @@ extern crate rug; use rayon::prelude::*; use rug::Float; -impl Renderer { +impl Render { pub fn render( - self, - iter_count_buffer: &mut [u32], - square_dist_buffer: &mut [f32], - fractal: &Fractal, - canvas_width: u32, - canvas_height: u32, + &mut self, + fractal: Fractal, + renderer: Renderer, centre: &Complex, zoom: &Float, extra: &Complex, max_iter_count: u32, ) { + assert!(max_iter_count > 0x0); + let data = RenderData::new( - iter_count_buffer, - square_dist_buffer, - canvas_width, - canvas_height, + &mut self.iter_count_buffer[..], + &mut self.square_dist_buffer[..], + self.canvas_width, + self.canvas_height, centre.clone(), extra.clone(), zoom.clone(), @@ -58,20 +57,21 @@ impl Renderer { fractal.inverse(), ); - let (canvas_size, overflow) = canvas_height.overflowing_mul(canvas_width); - if overflow { panic!("overflow when calculating canvas size") }; + let canvas_size = self.canvas_height as usize * self.canvas_width as usize; - let point_renderer = self.point_renderer(); + let point_renderer = renderer.point_renderer(); let iterator = fractal.iterator(); (0x0..canvas_size).into_par_iter().for_each(|index| { render_point(&data, index as usize, point_renderer, iterator); }); + + self.info = Some((fractal, max_iter_count)); } } fn render_point(data: &RenderData, index: usize, point_renderer: PointRenderer, iterator: IteratorFunction) { - let (iter_count_buffer, square_dist_buffer) = data.output_buffers(); + let (iter_count_buffer, square_dist_buffer) = unsafe { data.output_buffers() }; let (canvas_width, _) = data.canvas_size(); @@ -80,10 +80,6 @@ fn render_point(data: &RenderData, index: usize, point_renderer: PointRenderer, let (iter_count, square_dist) = point_renderer(&data, x, y, iterator); - // Sacrifice safety for speed by removing bounds- - // checking. - unsafe { - *iter_count_buffer.get_unchecked_mut( index as usize) = iter_count; - *square_dist_buffer.get_unchecked_mut(index as usize) = square_dist; - } + iter_count_buffer[ index as usize] = iter_count; + square_dist_buffer[index as usize] = square_dist; } diff --git a/source/benoit/benoit/render/render_data.rs b/source/benoit/benoit/render_data.rs index 86a21ee..0dda5d9 100644 --- a/source/benoit/benoit/render/render_data.rs +++ b/source/benoit/benoit/render_data.rs @@ -94,19 +94,6 @@ impl RenderData { } #[must_use] - pub fn inverse_factor(&self, val: &Complex) -> Float { - return if self.inverse { - let mut inverse_factor = Float::with_val(PRECISION, &val.real * &val.real); - inverse_factor += &val.imag * &val.imag; - inverse_factor.recip_mut(); - - inverse_factor - } else { - Float::with_val(PRECISION, 0x1) - }; - } - - #[must_use] pub fn canvas_size(&self) -> (u32, u32) { return (self.canvas_width, self.canvas_height); } @@ -117,9 +104,13 @@ impl RenderData { } #[must_use] - pub fn output_buffers(&self) -> (&mut [u32], &mut [f32]) { - let iter_count = unsafe { from_raw_parts_mut(self.iter_count_buffer, self.canvas_size as usize) }; - let square_dist = unsafe { from_raw_parts_mut(self.square_dist_buffer, self.canvas_size as usize) }; + pub unsafe fn output_buffers<'a>(&'a self) -> (&'a mut [u32], &'a mut [f32]) { + // All reads and writes to these buffer are unsafe + // AND UB if the same indices are used from + // multiple threads. + + let iter_count = from_raw_parts_mut(self.iter_count_buffer, self.canvas_size as usize); + let square_dist = from_raw_parts_mut(self.square_dist_buffer, self.canvas_size as usize); return (iter_count, square_dist); } @@ -128,6 +119,19 @@ impl RenderData { pub fn consts(&self) -> (f32, f32, f32, f32) { return (self.x_offset, self.y_offset, self.x_factor, self.y_factor); } + + #[must_use] + pub fn inverse_factor(&self, val: &Complex) -> Float { + return if self.inverse { + let mut inverse_factor = Float::with_val(PRECISION, &val.real * &val.real); + inverse_factor += &val.imag * &val.imag; + inverse_factor.recip_mut(); + + inverse_factor + } else { + Float::with_val(PRECISION, 0x1) + }; + } } unsafe impl Send for RenderData {} diff --git a/source/benoit/benoit/renderer.rs b/source/benoit/benoit/renderer.rs index 6b9aaca..0c02509 100644 --- a/source/benoit/benoit/renderer.rs +++ b/source/benoit/benoit/renderer.rs @@ -21,21 +21,24 @@ If not, see <https://www.gnu.org/licenses/>. */ -use crate::benoit::render::PointRenderer; -use crate::benoit::render::render_point; +use crate::benoit::fractal::IteratorFunction; +use crate::benoit::render_data::RenderData; use std::mem::transmute; -pub mod colour; -pub mod render; +mod render_point; #[derive(Clone, Copy)] +#[repr(u8)] pub enum Renderer { Julia, Normal, } +pub type PointRenderer = fn(&RenderData, u32, u32, IteratorFunction) -> (u32, f32); + impl Renderer { + #[must_use] pub fn point_renderer(self) -> PointRenderer { return match self { Renderer::Julia => render_point::julia, @@ -43,11 +46,10 @@ impl Renderer { }; } - pub fn cycle(self) -> Self { - let raw = !(self as u8) & 0b00000001; - + pub fn toggle(&mut self) { + let raw = !(*self as u8) & 0b00000001; let new: Self = unsafe { transmute(raw) }; - return new; + *self = new; } } diff --git a/source/benoit/benoit/render/render_point.rs b/source/benoit/benoit/renderer/render_point.rs index 7f9bf9a..7f9bf9a 100644 --- a/source/benoit/benoit/render/render_point.rs +++ b/source/benoit/benoit/renderer/render_point.rs diff --git a/source/benoit/benoit/render/render_point/julia.rs b/source/benoit/benoit/renderer/render_point/julia.rs index 63cd4cb..06a478c 100644 --- a/source/benoit/benoit/render/render_point/julia.rs +++ b/source/benoit/benoit/renderer/render_point/julia.rs @@ -23,8 +23,8 @@ use crate::benoit::{BAILOUT, PRECISION}; use crate::benoit::complex::Complex; -use crate::benoit::render::IteratorFunction; -use crate::benoit::render::render_data::RenderData; +use crate::benoit::fractal::IteratorFunction; +use crate::benoit::render_data::RenderData; extern crate rug; diff --git a/source/benoit/benoit/render/render_point/normal.rs b/source/benoit/benoit/renderer/render_point/normal.rs index 167bdb6..e9f848f 100644 --- a/source/benoit/benoit/render/render_point/normal.rs +++ b/source/benoit/benoit/renderer/render_point/normal.rs @@ -23,8 +23,8 @@ use crate::benoit::{BAILOUT, PRECISION}; use crate::benoit::complex::Complex; -use crate::benoit::render::IteratorFunction; -use crate::benoit::render::render_data::RenderData; +use crate::benoit::fractal::IteratorFunction; +use crate::benoit::render_data::RenderData; extern crate rug; diff --git a/source/benoit/benoit/script.rs b/source/benoit/benoit/script.rs new file mode 100644 index 0000000..4337944 --- /dev/null +++ b/source/benoit/benoit/script.rs @@ -0,0 +1,61 @@ +/* + 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::complex::Complex; +use crate::benoit::fractal::Fractal; +use crate::benoit::image::ImageFormat; +use crate::benoit::palette::Palette; +use crate::benoit::renderer::Renderer; + +extern crate rug; + +use rug::Float; + +pub mod animate; +pub mod configure; +pub mod dump; +pub mod run; +pub mod still; + +pub struct Script { + // Configuration: + fractal: Fractal, + renderer: Renderer, + + canvas_width: u32, + canvas_height: u32, + frame_start: u32, + frame_stop: u32, + + centre: Complex, + extra: Complex, + zoom: Float, + + max_iter_count: u32, + + palette: Palette, + colour_range: f32, + + dump_path: String, + image_format: ImageFormat, +} diff --git a/source/benoit/benoit/script/animate.rs b/source/benoit/benoit/script/animate.rs new file mode 100644 index 0000000..65bbe90 --- /dev/null +++ b/source/benoit/benoit/script/animate.rs @@ -0,0 +1,114 @@ +/* + 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::PRECISION; +use crate::benoit::image::Image; +use crate::benoit::render::Render; +use crate::benoit::script::Script; + +extern crate rug; + +use rug::Float; +use rug::ops::PowAssign; + +impl Script { + #[must_use] + pub(super) fn animate(&self) -> i32 { + let frame_count = self.frame_stop - self.frame_start + 0x1; + + let mut image = Image::allocate( self.canvas_width, self.canvas_height); + let mut render = Render::allocate(self.canvas_width, self.canvas_height); + + // zoom_start: + let mut zoom = Float::with_val(PRECISION, 1.0 / 4.0); + + let zoom_stop = Float::with_val(PRECISION, &self.zoom); + + let zoom_factor = get_zoom_factor(&zoom, &zoom_stop, self.frame_stop); + + zoom = if self.frame_start > 0x0 { + let mut zoom = zoom_factor.clone(); + zoom.pow_assign(frame_count); + + zoom + } else { + zoom + }; + + 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}"); + + Script::dump( + self.dump_path.as_str(), + frame_name.as_str(), + &mut image, + &mut render, + self.renderer, + self.fractal, + self.palette, + &self.centre, + &self.extra, + &zoom, + self.max_iter_count, + self.colour_range, + self.image_format, + ); + + zoom *= &zoom_factor; + } + + return 0x0; + } +} + +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); + + return factor; +} diff --git a/source/benoit/benoit/script/configure.rs b/source/benoit/benoit/script/configure.rs new file mode 100644 index 0000000..acb6071 --- /dev/null +++ b/source/benoit/benoit/script/configure.rs @@ -0,0 +1,54 @@ +/* + 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::complex::Complex; +use crate::benoit::configuration::Configuration; +use crate::benoit::script::Script; + +impl Script { + #[must_use] + pub fn configure(configuration: Configuration) -> Script { + return Script { + fractal: configuration.fractal, + renderer: configuration.renderer, + + canvas_width: configuration.canvas_width, + canvas_height: configuration.canvas_height, + frame_start: configuration.frame_start, + frame_stop: configuration.frame_stop, + + centre: Complex::new(configuration.centre_real, configuration.centre_imag), + zoom: configuration.zoom, + + extra: Complex::new(configuration.extra_real, configuration.extra_imag), + + max_iter_count: configuration.max_iter_count, + + palette: configuration.palette, + colour_range: configuration.colour_range, + + dump_path: configuration.dump_path, + image_format: configuration.image_format, + }; + } +} diff --git a/source/benoit/benoit/script/dump.rs b/source/benoit/benoit/script/dump.rs new file mode 100644 index 0000000..d77b597 --- /dev/null +++ b/source/benoit/benoit/script/dump.rs @@ -0,0 +1,79 @@ +/* + 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::complex::Complex; +use crate::benoit::fractal::Fractal; +use crate::benoit::image::{Image, ImageFormat}; +use crate::benoit::palette::Palette; +use crate::benoit::render::Render; +use crate::benoit::renderer::Renderer; +use crate::benoit::script::Script; + +extern crate rug; + +use rug::Float; +use std::time::Instant; + +impl Script { + pub(super) fn dump( + dump_path: &str, + name: &str, + image: &mut Image, + render: &mut Render, + renderer: Renderer, + fractal: Fractal, + palette: Palette, + 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(); + + render.render( + fractal, + renderer, + centre, + zoom, + extra, + max_iter_count, + ); + + let render_time = time_start.elapsed(); + eprint!(" {:.3}ms, colouring...", render_time.as_micros() as f32 / 1000.0); + + image.colour(&render, palette, max_iter_count, colour_range); + + 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}"); + + image.dump(path.as_str(), image_format); + eprintln!(" done"); + } +} diff --git a/source/benoit/benoit/script/run.rs b/source/benoit/benoit/script/run.rs new file mode 100644 index 0000000..24a34b0 --- /dev/null +++ b/source/benoit/benoit/script/run.rs @@ -0,0 +1,36 @@ +/* + 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::script::Script; + +impl Script { + #[must_use] + pub fn run(self) -> i32 { + let code = match self.frame_stop == 0x0 && self.frame_start == 0x0 { + false => self.animate(), + true => self.still(), + }; + + return code; + } +} diff --git a/source/benoit/benoit/script/still.rs b/source/benoit/benoit/script/still.rs new file mode 100644 index 0000000..73ebcf8 --- /dev/null +++ b/source/benoit/benoit/script/still.rs @@ -0,0 +1,54 @@ +/* + 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::image::Image; +use crate::benoit::render::Render; +use crate::benoit::script::Script; + +impl Script { + #[must_use] + pub(super) fn still(&self) -> i32 { + let mut image = Image::allocate( self.canvas_width, self.canvas_height); + let mut render = Render::allocate(self.canvas_width, self.canvas_height); + + const FRAME_NAME: &str = "render"; + + Script::dump( + self.dump_path.as_str(), + FRAME_NAME, + &mut image, + &mut render, + self.renderer, + self.fractal, + self.palette, + &self.centre, + &self.extra, + &self.zoom, + self.max_iter_count, + self.colour_range, + self.image_format, + ); + + return 0x0; + } +} diff --git a/source/benoit/benoit/video/draw.rs b/source/benoit/benoit/video/draw.rs index a3d2460..69b77b2 100644 --- a/source/benoit/benoit/video/draw.rs +++ b/source/benoit/benoit/video/draw.rs @@ -21,6 +21,7 @@ If not, see <https://www.gnu.org/licenses/>. */ +use crate::benoit::image::Image; use crate::benoit::video::Video; extern crate sdl2; @@ -29,21 +30,16 @@ use sdl2::pixels::Color; use sdl2::rect::Rect; impl Video { - pub fn draw(&mut self, image: &[u8], canvas_width: u32, canvas_height: u32, scale: u32) { - self.draw_image(image, canvas_width, canvas_height, scale); - } - - fn draw_image(&mut self, image: &[u8], canvas_width: u32, canvas_height: u32, scale: u32) { - let canvas_size = canvas_height * canvas_width; + pub fn draw(&mut self, image: &Image, scale: u32) { + let (canvas_width, canvas_height) = image.size(); + let canvas_size = canvas_height as usize * canvas_width as usize; for pixel in 0x0..canvas_size { + let x = pixel as u32 % canvas_width; let y = pixel as u32 / canvas_width; - let x = pixel as u32 - y * canvas_width; let colour = { - let red = image[pixel as usize * 0x3]; - let green = image[pixel as usize * 0x3 + 0x1]; - let blue = image[pixel as usize * 0x3 + 0x2]; + let (red, green, blue) = image[pixel as usize]; Color::RGB(red, green, blue) }; diff --git a/source/benoit/benoit/video/initialise.rs b/source/benoit/benoit/video/initialise.rs index b06d774..76668bd 100644 --- a/source/benoit/benoit/video/initialise.rs +++ b/source/benoit/benoit/video/initialise.rs @@ -30,6 +30,7 @@ use sdl2::pixels::Color; use sdl2::render::BlendMode; impl Video { + #[must_use] pub fn initialise(canvas_width: u32, canvas_height: u32, scale: u32) -> Video { let sdl = sdl2::init().expect("unable to initialise sdl2"); let sdl_video = sdl.video().expect("unable to initialise video"); diff --git a/source/benoit/main.rs b/source/benoit/main.rs index 3716309..9e8e572 100644 --- a/source/benoit/main.rs +++ b/source/benoit/main.rs @@ -23,15 +23,56 @@ mod benoit; -use benoit::app::App; +use crate::benoit::VERSION; +use crate::benoit::app::App; +use crate::benoit::configuration::Configuration; +use crate::benoit::script::Script; -use std::mem::drop; +extern crate rayon; + +use rayon::ThreadPoolBuilder; +use std::env::args; use std::process::exit; +use std::thread::available_parallelism; fn main() { - let mut app = App::initialise(); - let code = app.run(); + println!(); + println!("\u{1B}[1mBENO\u{CE}T\u{1B}[0m {:X}.{:X}.{:X}", VERSION[0x0], VERSION[0x1], VERSION[0x2]); + println!("Copyright 2021, 2023 Gabriel Bj\u{F8}rnager Jensen."); + println!(); + println!("Le p\u{E8}re cogita et c'est pourquoi il fut."); + println!(); + + let mut arguments = args(); + + let (mut configuration, interative) = match arguments.nth(0x1) { + Some(path) => (Configuration::load(path.as_str()), false), + _ => (Configuration::default(), true), + }; + + configuration.thread_count = if configuration.thread_count == 0x0 { + match available_parallelism() { + Ok(ammount) => ammount.get() as u32, + _ => 0x2, // We assume at least two threads. + } + } else { + configuration.thread_count + }; + + eprintln!("using {} threads", configuration.thread_count); + ThreadPoolBuilder::new().num_threads(configuration.thread_count as usize).build_global().unwrap(); + + let code = if interative { + eprintln!("running iteractive mode"); + + let app = App::configure(configuration); + app.run() + } else { + eprintln!("running script mode"); + + let script = Script::configure(configuration); + script.run() + }; - drop(app); exit(code); } |