diff options
37 files changed, 467 insertions, 692 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index c09d5b4..56c5591 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,14 @@ +# 2.1.0 + +* Refactor code structure +* Pre-calculate palettes +* Depend on ctor +* Remove factoriser functions +* Depend on enum-iterator +* Bump minor version +* Bump dependency versions +* Fix control guide + # 2.0.0 * Bump major version @@ -1,6 +1,6 @@ [package] name = "benoit" -version = "2.0.0" +version = "2.1.0" authors = ["Gabriel Bjørnager Jensen"] edition = "2021" description = "Mandelbrot renderer." @@ -16,9 +16,11 @@ codegen-units = 1 lto = "fat" [dependencies] -png = "0.17.10" -rayon = "1.8.0" -rug = "1.22.0" -sdl2 = "0.35.2" -toml = "0.8.0" -webp = "0.2.6" +ctor = "0.2.5" +enum-iterator = "1.4.1" +png = "0.17.10" +rayon = "1.8.0" +rug = "1.22.0" +sdl2 = "0.35.2" +toml = "0.8.2" +webp = "0.2.6" diff --git a/source/benoit/benoit.rs b/source/benoit/benoit.rs index bbbc158..1214d16 100644 --- a/source/benoit/benoit.rs +++ b/source/benoit/benoit.rs @@ -27,7 +27,6 @@ use rug::Float; pub mod app; pub mod configuration; -pub mod factorisation; pub mod fractal; pub mod palette; pub mod rendering; @@ -45,18 +44,12 @@ pub struct Version<T> { pub const VERSION: Version::<u32> = Version::<u32> { major: 0x2, - minor: 0x0, + minor: 0x1, patch: 0x0, }; pub const PRECISION: u32 = 0x80; -// We would like to precalculate the palettes at -// compile-time, but Rust does not support -// floating-point arithmetic there. -#[allow(dead_code)] -pub const PALETTE_LENGTH: usize = 0x100; - #[derive(Clone, Copy)] pub enum ImageFormat { Png, diff --git a/source/benoit/benoit/app.rs b/source/benoit/benoit/app.rs index 1863a77..feac808 100644 --- a/source/benoit/benoit/app.rs +++ b/source/benoit/benoit/app.rs @@ -22,11 +22,10 @@ */ use crate::benoit::ImageFormat; -use crate::benoit::factorisation::Factorisation; use crate::benoit::fractal::Fractal; use crate::benoit::palette::Palette; use crate::benoit::rendering::Rendering; -use crate::benoit::render::{FactoriserFunction, IteratorFunction, PaletteFunction, RowRenderer}; +use crate::benoit::render::{IteratorFunction, RowRenderer}; use crate::benoit::video::Video; extern crate rug; @@ -35,8 +34,6 @@ use rug::Float; pub mod allocate_buffers; pub mod animate; -pub mod colour; -pub mod colour_row; pub mod drop; pub mod dump; pub mod handle_keys; @@ -44,7 +41,6 @@ pub mod image_filename; pub mod initialise; pub mod interactive; pub mod poll_events; -pub mod render; pub mod run; pub mod still; @@ -68,9 +64,8 @@ pub struct App { max_iter_count: u32, - factorisation: Factorisation, - palette: Palette, - colour_range: f32, + palette: Palette, + colour_range: f32, dump_path: String, image_format: ImageFormat, @@ -83,7 +78,4 @@ pub struct App { row_renderer: RowRenderer, iterator_function: IteratorFunction, - - factoriser: FactoriserFunction, - palette_function: PaletteFunction, } diff --git a/source/benoit/benoit/app/allocate_buffers.rs b/source/benoit/benoit/app/allocate_buffers.rs index b8616d5..44bbb28 100644 --- a/source/benoit/benoit/app/allocate_buffers.rs +++ b/source/benoit/benoit/app/allocate_buffers.rs @@ -24,6 +24,7 @@ use crate::benoit::app::App; 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; diff --git a/source/benoit/benoit/app/animate.rs b/source/benoit/benoit/app/animate.rs index 7cc3549..3d35bdb 100644 --- a/source/benoit/benoit/app/animate.rs +++ b/source/benoit/benoit/app/animate.rs @@ -23,6 +23,7 @@ use crate::benoit::PRECISION; use crate::benoit::app::App; +use crate::benoit::render::{colour, render}; extern crate rug; extern crate sdl2; @@ -71,19 +72,19 @@ impl App { factor }; - eprintln!("animating {} frames at {}{:+}i to {:.3} (fac. {:.3})", self.frame_count, self.centre_real.to_f64(), self.centre_imag.to_f64(), zoom_stop.to_f64(), zoom_factor.to_f64()); + 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(); - self.render(&mut iter_count_buffer[..], &mut square_dist_buffer[..], &self.centre_real, &self.centre_imag, &zoom, self.max_iter_count); + 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.row_renderer, self.iterator_function); let render_time = time_start.elapsed(); eprint!(" {:.3}ms, colouring...", render_time.as_micros() as f32 / 1000.0); - self.colour(&mut image[..], self.multibrot_exponent, self.max_iter_count, &mut iter_count_buffer[..], &mut square_dist_buffer[..]); + 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); diff --git a/source/benoit/benoit/app/colour.rs b/source/benoit/benoit/app/colour.rs deleted file mode 100644 index 03093ac..0000000 --- a/source/benoit/benoit/app/colour.rs +++ /dev/null @@ -1,43 +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_data::ColourData; - -extern crate rayon; - -use rayon::prelude::*; -use std::sync::Arc; - -impl App { - pub fn colour(&self, buffer: &mut [u8], multibrot_exponent: f32, max_iter_count: u32, iter_count_buffer: &[u32], square_dist_buffer: &[f32]) { - let data = Arc::new(ColourData::new(buffer, self.canvas_width, multibrot_exponent, max_iter_count, self.colour_range, iter_count_buffer, square_dist_buffer)); - - let factoriser = self.factoriser; - let palette_function = self.palette_function; - - (0x0..self.canvas_height).into_par_iter().for_each(|row| { - App::colour_row(data.clone(), row as u32, factoriser, palette_function); - }); - } -} diff --git a/source/benoit/benoit/app/colour_row.rs b/source/benoit/benoit/app/colour_row.rs deleted file mode 100644 index a4baca7..0000000 --- a/source/benoit/benoit/app/colour_row.rs +++ /dev/null @@ -1,61 +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::{FactoriserFunction, PaletteFunction}; -use crate::benoit::render::colour_data::ColourData; - -use std::sync::Arc; - -impl App { - pub fn colour_row(data: Arc<ColourData>, y: u32, factoriser: FactoriserFunction, palette_function: PaletteFunction) { - let (iter_count_buffer, square_dist_buffer, image) = unsafe { data.slice(y) }; - - for x in 0x0..data.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 factor = if iter_count < data.max_iter_count { - factoriser(iter_count, distance, data.colour_range, data.exponent) - } else { - f32::NAN - }; - - let (red, green, blue) = palette_function(factor); - - 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/app/handle_keys.rs b/source/benoit/benoit/app/handle_keys.rs index da88bfc..0dc5b5a 100644 --- a/source/benoit/benoit/app/handle_keys.rs +++ b/source/benoit/benoit/app/handle_keys.rs @@ -21,12 +21,11 @@ If not, see <https://www.gnu.org/licenses/>. */ -use crate::benoit::PRECISION; +use crate::benoit::{PRECISION}; use crate::benoit::app::App; -use crate::benoit::factorisation::Factorisation; use crate::benoit::fractal::Fractal; use crate::benoit::palette::Palette; -use crate::benoit::render::{FactoriserFunction, IteratorFunction, PaletteFunction, RowRenderer}; +use crate::benoit::render::{IteratorFunction, RowRenderer}; use crate::benoit::rendering::Rendering; extern crate rug; @@ -35,17 +34,19 @@ extern crate sdl2; use rug::Float; use sdl2::keyboard::Scancode; +pub const MIN_COLOUR_RANGE: f32 = 2.0; + impl App { + #[must_use] pub fn handle_keys(&mut self, scan_code: Scancode) -> bool { 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.factorisation, self.factoriser) = toggle_smooth(self.factorisation), - Scancode::Left => (self.palette, self.palette_function) = cycle_palette(self.palette, -0x1), + 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, self.palette_function) = cycle_palette(self.palette, 0x1), + Scancode::Right => self.palette = cycle_palette(self.palette, 0x1), Scancode::Tab => (self.rendering, self.row_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), _ => {}, @@ -63,7 +64,7 @@ impl App { self.colour_range = match scan_code { Scancode::Up => self.colour_range * COLOUR_RANGE_FACTOR, - Scancode::Down => (self.colour_range / COLOUR_RANGE_FACTOR).max(2.0), + Scancode::Down => (self.colour_range / COLOUR_RANGE_FACTOR).max(MIN_COLOUR_RANGE), _ => self.colour_range, }; @@ -125,24 +126,10 @@ fn toggle_julia(rendering: Rendering) -> (Rendering, RowRenderer) { return (rendering, row_renderer); } -fn toggle_smooth(factorisation: Factorisation) -> (Factorisation, FactoriserFunction) { - let factorisation = factorisation.cycle(); - - let factoriser = factorisation.get_factoriser(); - - match factorisation { - Factorisation::Smooth => eprintln!("disabled smoothing"), - Factorisation::Stepped => eprintln!("enabled smoothing"), - }; - - return (factorisation, factoriser); -} - -fn cycle_palette(palette: Palette, direction: i8) -> (Palette, PaletteFunction) { - let palette = palette + direction; - let palette_function = palette.get_function(); +fn cycle_palette(palette: Palette, direction: i8) -> Palette { + let palette = palette.cycle(direction); eprintln!("using palette \"{}\"", palette.get_name()); - return (palette, palette_function); + return palette; } diff --git a/source/benoit/benoit/app/image_filename.rs b/source/benoit/benoit/app/image_filename.rs index 9ab111a..7abc71a 100644 --- a/source/benoit/benoit/app/image_filename.rs +++ b/source/benoit/benoit/app/image_filename.rs @@ -25,6 +25,7 @@ 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", diff --git a/source/benoit/benoit/app/initialise.rs b/source/benoit/benoit/app/initialise.rs index 6a4d385..75535e8 100644 --- a/source/benoit/benoit/app/initialise.rs +++ b/source/benoit/benoit/app/initialise.rs @@ -32,6 +32,7 @@ use std::env::args; use std::thread::available_parallelism; impl App { + #[must_use] pub fn initialise() -> App { let mut arguments = args(); @@ -77,9 +78,8 @@ impl App { max_iter_count: configuration.max_iter_count, - factorisation: configuration.factorisation, - palette: configuration.palette, - colour_range: configuration.colour_range, + palette: configuration.palette, + colour_range: configuration.colour_range, dump_path: configuration.dump_path, image_format: configuration.image_format, @@ -92,9 +92,6 @@ impl App { row_renderer: configuration.rendering.get_row_renderer(), iterator_function: configuration.fractal.get_iterator(), - - factoriser: configuration.factorisation.get_factoriser(), - palette_function: configuration.palette.get_function(), }; } } diff --git a/source/benoit/benoit/app/interactive.rs b/source/benoit/benoit/app/interactive.rs index f03576d..e4ecd7c 100644 --- a/source/benoit/benoit/app/interactive.rs +++ b/source/benoit/benoit/app/interactive.rs @@ -23,6 +23,7 @@ use crate::benoit::FeedbackInfo; use crate::benoit::app::App; +use crate::benoit::render::{colour, render}; use crate::benoit::rendering::Rendering; use crate::benoit::video::Video; @@ -32,6 +33,7 @@ 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(); @@ -61,7 +63,7 @@ impl App { let time_start = Instant::now(); - self.render(&mut iter_count_buffer[..], &mut square_dist_buffer[..], &self.centre_real, &self.centre_imag, &self.zoom, self.max_iter_count); + 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.row_renderer, self.iterator_function); let render_time = time_start.elapsed(); eprintln!(" {:.3}ms", render_time.as_micros() as f32 / 1000.0); @@ -76,7 +78,7 @@ impl App { self.do_render = false; } - self.colour(&mut image[..], prev_multibrot_exponent, prev_max_iter_count.min(self.max_iter_count), &mut iter_count_buffer[..], &mut square_dist_buffer[..]); + 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[..]); video.draw(&image[..], self.canvas_width, self.canvas_height, self.scale); self.draw_feedback(&mut video, &prev_centre_real, &prev_centre_imag, &prev_zoom); @@ -144,9 +146,9 @@ impl App { println!("- \u{1B}[1mRight\u{1B}[0m Cycle to next palette"); println!(); println!("- \u{1B}[1mF1\u{1B}[0m Toggle textual feedback"); - println!("- \u{1B}[1mC\u{1B}[0m Print centre value (c)"); + println!("- \u{1B}[1mZ\u{1B}[0m Print centre value (c)"); println!(); - println!("- \u{1B}[1mSpace\u{1B}[0m Render frame"); + 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 f5e38b6..2ba81fa 100644 --- a/source/benoit/benoit/app/poll_events.rs +++ b/source/benoit/benoit/app/poll_events.rs @@ -29,6 +29,7 @@ use sdl2::EventPump; 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() { let quit = match event { diff --git a/source/benoit/benoit/app/run.rs b/source/benoit/benoit/app/run.rs index 9211716..b1ef578 100644 --- a/source/benoit/benoit/app/run.rs +++ b/source/benoit/benoit/app/run.rs @@ -27,6 +27,7 @@ use crate::benoit::app::App; extern crate sdl2; impl App { + #[must_use] pub fn run(&mut self) -> i32 { println!(); println!("\u{1B}[1mBENO\u{CE}T\u{1B}[0m {:X}.{:X}.{:X}", VERSION.major, VERSION.minor, VERSION.patch); diff --git a/source/benoit/benoit/app/still.rs b/source/benoit/benoit/app/still.rs index bd5ea9e..ff483b6 100644 --- a/source/benoit/benoit/app/still.rs +++ b/source/benoit/benoit/app/still.rs @@ -22,8 +22,7 @@ */ use crate::benoit::app::App; - -extern crate rug; +use crate::benoit::render::{colour, render}; use std::time::Instant; @@ -36,12 +35,12 @@ impl App { eprint!("rendering at {}{:+}i ({}x)...", self.centre_real.to_f32(), self.centre_imag.to_f32(), self.zoom.to_f32()); let time_start = Instant::now(); - self.render(&mut iter_count_buffer[..], &mut square_dist_buffer[..], &self.centre_real, &self.centre_imag, &self.zoom, self.max_iter_count); + 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.row_renderer, self.iterator_function); let render_time = time_start.elapsed(); eprint!(" {:.3}ms, colouring...", render_time.as_micros() as f32 / 1000.0); - self.colour(&mut image[..], self.multibrot_exponent, self.max_iter_count, &iter_count_buffer[..], &square_dist_buffer[..]); + 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); diff --git a/source/benoit/benoit/configuration.rs b/source/benoit/benoit/configuration.rs index 828ccae..ea6a841 100644 --- a/source/benoit/benoit/configuration.rs +++ b/source/benoit/benoit/configuration.rs @@ -21,18 +21,18 @@ If not, see <https://www.gnu.org/licenses/>. */ -use crate::benoit::ImageFormat; -use crate::benoit::factorisation::Factorisation; +use crate::benoit::{ImageFormat, PRECISION}; use crate::benoit::fractal::Fractal; use crate::benoit::palette::Palette; use crate::benoit::rendering::Rendering; extern crate rug; +extern crate toml; use rug::Float; - -pub mod default; -pub mod load; +use std::fs::read; +use std::str::FromStr; +use toml::{Table, Value}; pub struct Configuration { pub thread_count: u32, @@ -51,12 +51,180 @@ pub struct Configuration { pub max_iter_count: u32, - pub factorisation: Factorisation, - pub palette: Palette, - pub colour_range: f32, + pub palette: Palette, + pub colour_range: f32, pub dump_path: String, pub image_format: ImageFormat, pub interactive: bool, } + +impl Configuration { + #[must_use] + pub fn default() -> Configuration { + return Configuration { + thread_count: 0x0, + + fractal: Fractal::Mandelbrot, + rendering: Rendering::Normal, + + canvas_width: 0x100, + canvas_height: 0xC0, + scale: 0x2, + frame_count: 0x10, + + centre_real: Float::with_val(PRECISION, 0.0), + centre_imag: Float::with_val(PRECISION, 0.0), + zoom: Float::with_val(PRECISION, 1.0), + + max_iter_count: 0x100, + + palette: Palette::Fire, + colour_range: 64.0, + + dump_path: "./render".to_string(), + image_format: ImageFormat::Png, + + 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/default.rs b/source/benoit/benoit/configuration/default.rs deleted file mode 100644 index f7f30d7..0000000 --- a/source/benoit/benoit/configuration/default.rs +++ /dev/null @@ -1,64 +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::factorisation::Factorisation; -use crate::benoit::fractal::Fractal; -use crate::benoit::palette::Palette; -use crate::benoit::rendering::Rendering; -use crate::benoit::configuration::Configuration; - -extern crate rug; - -use rug::Float; - -impl Configuration { - pub fn default() -> Configuration { - return Configuration { - thread_count: 0x0, - - fractal: Fractal::Mandelbrot, - rendering: Rendering::Normal, - - canvas_width: 0x100, - canvas_height: 0xC0, - scale: 0x2, - frame_count: 0x10, - - centre_real: Float::with_val(PRECISION, 0.0), - centre_imag: Float::with_val(PRECISION, 0.0), - zoom: Float::with_val(PRECISION, 1.0), - - max_iter_count: 0x100, - - factorisation: Factorisation::Smooth, - palette: Palette::Fire, - colour_range: 64.0, - - 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 deleted file mode 100644 index 0b4b20f..0000000 --- a/source/benoit/benoit/configuration/load.rs +++ /dev/null @@ -1,184 +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::factorisation::Factorisation; -use crate::benoit::fractal::Fractal; -use crate::benoit::palette::Palette; -use crate::benoit::rendering::Rendering; -use crate::benoit::configuration::Configuration; - -extern crate rug; -extern crate toml; - -use rug::Float; -use std::fs::read; -use std::str::FromStr; -use toml::{Table, Value}; - -impl Configuration { - 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, "factorisation") { - configuration.factorisation = match name.as_str() { - "smooth" => Factorisation::Smooth, - "stepped" => Factorisation::Stepped, - name => panic!("invalid factorisation method \"{name}\""), - }; - } - - 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/factorisation.rs b/source/benoit/benoit/factorisation.rs deleted file mode 100644 index fc0f7ba..0000000 --- a/source/benoit/benoit/factorisation.rs +++ /dev/null @@ -1,50 +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::render::FactoriserFunction; -use crate::benoit::render::factorise; - -use std::mem::transmute; - -#[derive(Clone, Copy)] -pub enum Factorisation { - Smooth, - Stepped, -} - -impl Factorisation { - pub fn get_factoriser(self) -> FactoriserFunction { - return match self { - Factorisation::Smooth => factorise::smooth, - Factorisation::Stepped => factorise::stepped, - }; - } - - pub fn cycle(self) -> Self { - let raw = !(self as u8 != 0x0); - - let new: Self = unsafe { transmute(raw) }; - - return new; - } -} diff --git a/source/benoit/benoit/palette.rs b/source/benoit/benoit/palette.rs index 628c5f4..5477242 100644 --- a/source/benoit/benoit/palette.rs +++ b/source/benoit/benoit/palette.rs @@ -21,13 +21,16 @@ If not, see <https://www.gnu.org/licenses/>. */ -use crate::benoit::render::PaletteFunction; -use crate::benoit::render::paint; +extern crate ctor; +extern crate enum_iterator; +use ctor::ctor; +use enum_iterator::{all, Sequence}; use std::mem::transmute; -use std::ops::Add; -#[derive(Clone, Copy)] +mod paint; + +#[derive(Clone, Copy, Sequence)] pub enum Palette { Ancient, Fire, @@ -37,9 +40,45 @@ 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.get_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 MAX: u8 = Palette::Sapphire as u8; + const MIN: Self = Palette::Ancient; + const MAX: Self = Palette::Sapphire; + #[must_use] pub fn get_name(self) -> &'static str { return match self { Palette::Ancient => "ancient", @@ -51,7 +90,28 @@ impl Palette { }; } - pub fn get_function(self) -> PaletteFunction { + #[must_use] + pub fn get_data(self) -> &'static PaletteData { + return &*self.get_data_mut(); + } + + #[must_use] + pub fn cycle(&self, direction: i8) -> Self { + let raw = *self as i8 + direction; + + let new: Palette = if raw < 0x0 { + Palette::MAX + } else if raw > Palette::MAX as i8 { + Palette::MIN + } else { + unsafe { transmute(raw) } + }; + + return new; + } + + #[must_use] + fn get_function(self) -> fn(f32) -> (f32, f32, f32) { return match self { Palette::Ancient => paint::ancient, Palette::Fire => paint::fire, @@ -61,25 +121,16 @@ impl Palette { Palette::Sapphire => paint::sapphire, }; } -} - -impl Add<i8> for Palette { - type Output = Palette; - fn add(self, direction: i8) -> Self { - assert!(direction != 0x0); - - let raw = self as i8 + direction; - let raw: u8 = if raw < 0x0 { - Palette::MAX - } else if raw > Palette::MAX as i8 { - 0x0 - } else { - raw as u8 - }; - - let new: Self = unsafe { transmute(raw) }; - - return new; + #[must_use] + fn get_data_mut(self) -> &'static mut PaletteData { + return unsafe { match self { + Palette::Ancient => &mut DATA_ANCIENT, + Palette::Fire => &mut DATA_FIRE, + Palette::Greyscale => &mut DATA_GREYSCALE, + Palette::Hsv => &mut DATA_HSV, + Palette::Lch => &mut DATA_LCH, + Palette::Sapphire => &mut DATA_SAPPHIRE, + } }; } } diff --git a/source/benoit/benoit/render/paint.rs b/source/benoit/benoit/palette/paint.rs index ff3bd47..ff3bd47 100644 --- a/source/benoit/benoit/render/paint.rs +++ b/source/benoit/benoit/palette/paint.rs diff --git a/source/benoit/benoit/render/paint/ancient.rs b/source/benoit/benoit/palette/paint/ancient.rs index a2c01eb..cd11e1a 100644 --- a/source/benoit/benoit/render/paint/ancient.rs +++ b/source/benoit/benoit/palette/paint/ancient.rs @@ -27,15 +27,9 @@ pub fn ancient(factor: f32) -> (f32, f32, f32) { let factor = factor % 1.0; - let (red, green, blue) = if !factor.is_nan() { - let red = 9.0 * (1.0 - factor) * factor * factor * factor; - let green = 15.0 * (1.0 - factor) * (1.0 - factor) * factor * factor; - let blue = 8.5 * (1.0 - factor) * (1.0 - factor) * (1.0 - factor) * factor; - - (red, green, blue) - } else { - (0.0, 0.0, 0.0) - }; + let red = 9.0 * (1.0 - factor) * factor * factor * factor; + let green = 15.0 * (1.0 - factor) * (1.0 - factor) * factor * factor; + let blue = 8.5 * (1.0 - factor) * (1.0 - factor) * (1.0 - factor) * factor; return (red, green, blue); } diff --git a/source/benoit/benoit/render/paint/fire.rs b/source/benoit/benoit/palette/paint/fire.rs index 23ab2b0..9895210 100644 --- a/source/benoit/benoit/render/paint/fire.rs +++ b/source/benoit/benoit/palette/paint/fire.rs @@ -27,24 +27,20 @@ pub fn fire(factor: f32) -> (f32, f32, f32) { let factor = factor % 1.0; - let (red, green, blue) = if !factor.is_nan() { - if factor <= 1.0 / 4.0 { - (factor * 4.0, 0.0, 0.0) - } else if factor <= 1.0 / 2.0 { - let factor = factor - (1.0 / 4.0); - - (1.0, factor * 4.0, 0.0) - } else if factor <= 3.0 / 4.0 { - let factor = factor - (1.0 / 2.0); - - (1.0, 1.0, factor * 4.0) - } else { - let factor = 1.0 - factor; - - (factor * 4.0, factor * 4.0, factor * 4.0) - } + let (red, green, blue) = if factor <= 1.0 / 4.0 { + (factor * 4.0, 0.0, 0.0) + } else if factor <= 1.0 / 2.0 { + let factor = factor - (1.0 / 4.0); + + (1.0, factor * 4.0, 0.0) + } else if factor <= 3.0 / 4.0 { + let factor = factor - (1.0 / 2.0); + + (1.0, 1.0, factor * 4.0) } else { - (0.0, 0.0, 0.0) + let factor = 1.0 - factor; + + (factor * 4.0, factor * 4.0, factor * 4.0) }; return (red, green, blue); diff --git a/source/benoit/benoit/render/paint/greyscale.rs b/source/benoit/benoit/palette/paint/greyscale.rs index 977edf1..046f6b8 100644 --- a/source/benoit/benoit/render/paint/greyscale.rs +++ b/source/benoit/benoit/palette/paint/greyscale.rs @@ -33,11 +33,5 @@ pub fn greyscale(factor: f32) -> (f32, f32, f32) { factor }) * 2.0; - let (red, green, blue) = if !factor.is_nan() { - (factor, factor, factor) - } else { - (0.0, 0.0, 0.0) - }; - - return (red, green, blue); + return (factor, factor, factor); } diff --git a/source/benoit/benoit/render/paint/hsv.rs b/source/benoit/benoit/palette/paint/hsv.rs index e6b414b..7f04261 100644 --- a/source/benoit/benoit/render/paint/hsv.rs +++ b/source/benoit/benoit/palette/paint/hsv.rs @@ -24,13 +24,7 @@ use std::hint::unreachable_unchecked; pub fn hsv(factor: f32) -> (f32, f32, f32) { - let (red, green, blue) = if !factor.is_nan() { - hsv_to_rgb(factor, 7.0 / 8.0, 7.0 / 8.0) - } else { - (0.0, 0.0, 0.0) - }; - - return (red, green, blue); + return hsv_to_rgb(factor, 7.0 / 8.0, 7.0 / 8.0); } fn hsv_to_rgb(hue: f32, saturation: f32, value: f32) -> (f32, f32, f32) { diff --git a/source/benoit/benoit/render/paint/lch.rs b/source/benoit/benoit/palette/paint/lch.rs index 38dfa0f..51d2ac0 100644 --- a/source/benoit/benoit/render/paint/lch.rs +++ b/source/benoit/benoit/palette/paint/lch.rs @@ -24,23 +24,17 @@ use std::f32::consts::PI; pub fn lch(factor: f32) -> (f32, f32, f32) { - let (red, green, blue) = if !factor.is_nan() { - // Convert turns to radians: - // 1 turn = 2pi radians + // Convert turns to radians: + // 1 turn = 2pi radians - let angle = factor * PI; + let angle = factor * PI * 2.0; - let (l, a, b) = lch_to_lab( 200.0 / 3.0, 1053.0 / 20.0, angle); - let (x, y, z) = lab_to_xyz( l, a, b); - let (r, g, b) = xyz_to_rgb( x, y, z); - let (r, g, b) = rgb_to_srgb(r, g, b); + let (l, a, b) = lch_to_lab( 200.0 / 3.0, 1053.0 / 20.0, angle); + let (x, y, z) = lab_to_xyz( l, a, b); + let (r, g, b) = xyz_to_rgb( x, y, z); + let (r, g, b) = rgb_to_srgb(r, g, b); - (r, g, b) - } else { - (0.0, 0.0, 0.0) - }; - - return (red, green, blue); + return (r, g, b); } fn rgb_to_srgb(r: f32, g: f32, b: f32) -> (f32, f32, f32) { diff --git a/source/benoit/benoit/render/paint/sapphire.rs b/source/benoit/benoit/palette/paint/sapphire.rs index 4f3e29a..9ae4b0c 100644 --- a/source/benoit/benoit/render/paint/sapphire.rs +++ b/source/benoit/benoit/palette/paint/sapphire.rs @@ -30,11 +30,5 @@ pub fn sapphire(factor: f32) -> (f32, f32, f32) { factor }) * 2.0; - let (red, green, blue) = if !factor.is_nan() { - (factor * factor * factor, factor * factor, factor) - } else { - (0.0, 0.0, 0.0) - }; - - return (red, green, blue); + return (factor * factor * factor, factor * factor, factor); } diff --git a/source/benoit/benoit/render.rs b/source/benoit/benoit/render.rs index ddea2f6..b6c0ac8 100644 --- a/source/benoit/benoit/render.rs +++ b/source/benoit/benoit/render.rs @@ -28,20 +28,16 @@ extern crate rug; use rug::Float; use std::sync::Arc; +pub mod colour; pub mod colour_data; -pub mod factorise; pub mod iterate; -pub mod paint; +pub mod render; pub mod render_data; pub mod render_row; +pub use colour::*; +pub use render::*; + pub type IteratorFunction = fn(&mut Float, &mut Float, &Float, &Float); pub type RowRenderer = fn(Arc<RenderData>, u32, IteratorFunction); - -// We pass the multibrot exponent to the factoriser -// as it is important with regard to smoothing, as -// the distance grows according to this exponent. -pub type FactoriserFunction = fn(u32, f32, f32, f32) -> f32; - -pub type PaletteFunction = fn(f32) -> (f32, f32, f32); diff --git a/source/benoit/benoit/render/colour.rs b/source/benoit/benoit/render/colour.rs new file mode 100644 index 0000000..9debac4 --- /dev/null +++ b/source/benoit/benoit/render/colour.rs @@ -0,0 +1,74 @@ +/* + 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 1d8e278..33123db 100644 --- a/source/benoit/benoit/render/colour_data.rs +++ b/source/benoit/benoit/render/colour_data.rs @@ -21,14 +21,19 @@ If not, see <https://www.gnu.org/licenses/>. */ +use crate::benoit::palette::{Palette, PaletteData}; + use std::slice::{from_raw_parts, from_raw_parts_mut}; pub struct ColourData { - pub canvas_width: u32, + canvas_width: u32, + canvas_height: u32, + + exponent: f32, + max_iter_count: u32, + colour_range: f32, - pub exponent: f32, - pub max_iter_count: u32, - pub colour_range: f32, + palette_data: &'static PaletteData, iter_count_buffer: *const u32, square_dist_buffer: *const f32, @@ -37,14 +42,18 @@ pub struct ColourData { } impl ColourData { - pub fn new(image: &mut [u8], canvas_width: u32, exponent: f32, max_iter_count: u32, colour_range: f32, iter_count_buffer: &[u32], square_dist_buffer: &[f32]) -> 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_width: canvas_width, + canvas_height: canvas_height, exponent: exponent, max_iter_count: max_iter_count, colour_range: colour_range, + palette_data: palette.get_data(), + iter_count_buffer: iter_count_buffer.as_ptr(), square_dist_buffer: square_dist_buffer.as_ptr(), @@ -52,17 +61,32 @@ impl ColourData { }; } - pub unsafe fn slice(&self, row: u32) -> (&[u32], &[f32], &mut [u8]) { - let offset = row as isize * self.canvas_width as isize; + #[must_use] + pub fn input_buffers(&self, row: u32) -> (&[u32], &[f32]) { + assert!(row < self.canvas_height); - let iter_count = from_raw_parts(self.iter_count_buffer.offset(offset), self.canvas_width as usize); - let dist = from_raw_parts(self.square_dist_buffer.offset(offset), self.canvas_width as usize); + let offset = row as usize * self.canvas_width as usize; - let offset = offset * 0x3; + 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) }; + + return (iter_count, dist); + } - let image = from_raw_parts_mut(self.image.offset(offset), self.canvas_width as usize * 0x3); + #[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) }; + + return image; + } - return (iter_count, dist, 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); } } diff --git a/source/benoit/benoit/render/factorise.rs b/source/benoit/benoit/render/factorise.rs deleted file mode 100644 index ab2f75e..0000000 --- a/source/benoit/benoit/render/factorise.rs +++ /dev/null @@ -1,28 +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/>. -*/ - -pub mod smooth; -pub mod stepped; - -pub use smooth::*; -pub use stepped::*; diff --git a/source/benoit/benoit/render/factorise/smooth.rs b/source/benoit/benoit/render/factorise/smooth.rs deleted file mode 100644 index b324af5..0000000 --- a/source/benoit/benoit/render/factorise/smooth.rs +++ /dev/null @@ -1,30 +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/>. -*/ - -// Classic colour palette, fixed from version ↋. - -pub fn smooth(iter_count: u32, distance: f32, colour_range: f32, exponent: f32) -> f32 { - let factor = (iter_count as f32 + 1.0 - distance.log(exponent).log(exponent)) / colour_range; - - return factor; -} diff --git a/source/benoit/benoit/render/factorise/stepped.rs b/source/benoit/benoit/render/factorise/stepped.rs deleted file mode 100644 index df9ac2d..0000000 --- a/source/benoit/benoit/render/factorise/stepped.rs +++ /dev/null @@ -1,31 +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/>. -*/ - -// Classic colour palette, fixed (and smoothed) -// from version ↋. - -pub fn stepped(iter_count: u32, _distance: f32, colour_range: f32, _exponent: f32) -> f32 { - let factor = iter_count as f32 / colour_range; - - return factor; -} diff --git a/source/benoit/benoit/app/render.rs b/source/benoit/benoit/render/render.rs index 6e4a382..947bca7 100644 --- a/source/benoit/benoit/app/render.rs +++ b/source/benoit/benoit/render/render.rs @@ -21,7 +21,7 @@ If not, see <https://www.gnu.org/licenses/>. */ -use crate::benoit::app::App; +use crate::benoit::render::{IteratorFunction, RowRenderer}; use crate::benoit::render::render_data::RenderData; extern crate rayon; @@ -31,15 +31,10 @@ use rayon::prelude::*; use rug::Float; use std::sync::Arc; -impl App { - pub fn render(&self, iter_count_buffer: &mut [u32], square_dist_buffer: &mut [f32], centre_real: &Float, centre_imag: &Float, zoom: &Float, max_iter_count: u32) { - let row_renderer = self.row_renderer; - let iterator = self.iterator_function; +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, row_renderer: RowRenderer, iterator: IteratorFunction) { + let data = Arc::new(RenderData::new(iter_count_buffer, square_dist_buffer, canvas_width, canvas_height, centre_real.clone(), centre_imag.clone(), zoom.clone(), max_iter_count)); - let data = Arc::new(RenderData::new(iter_count_buffer, square_dist_buffer, self.canvas_width, self.canvas_height, centre_real.clone(), centre_imag.clone(), zoom.clone(), max_iter_count)); - - (0x0..self.canvas_height).into_par_iter().for_each(|row| { - row_renderer(data.clone(), row as u32, iterator); - }); - } + (0x0..canvas_height).into_par_iter().for_each(|row| { + row_renderer(data.clone(), row as u32, iterator); + }); } diff --git a/source/benoit/benoit/render/render_data.rs b/source/benoit/benoit/render/render_data.rs index b0f0851..765c0c0 100644 --- a/source/benoit/benoit/render/render_data.rs +++ b/source/benoit/benoit/render/render_data.rs @@ -49,6 +49,7 @@ 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) -> RenderData { let (width_ratio, height_ratio) = width_height_ratio(canvas_width, canvas_height); @@ -73,15 +74,13 @@ impl RenderData { }; } + #[must_use] pub fn input(&self) -> (u32, &Float, &Float, &Float, u32) { return (self.canvas_width, &self.centre_real, &self.centre_imag, &self.zoom, self.max_iter_count); } - pub fn consts(&self) -> (f32, f32, f32, f32) { - return (self.x_offset, self.y_offset, self.x_factor, self.y_factor); - } - - pub fn output(&self, row: u32) -> (&mut [u32], &mut [f32]) { + #[must_use] + pub fn output_buffers(&self, row: u32) -> (&mut [u32], &mut [f32]) { assert!(row < self.canvas_height); let offset = row as isize * self.canvas_width as isize; @@ -91,6 +90,11 @@ impl RenderData { return (iter_count, square_dist); } + + #[must_use] + pub fn consts(&self) -> (f32, f32, f32, f32) { + return (self.x_offset, self.y_offset, self.x_factor, self.y_factor); + } } unsafe impl Send for RenderData {} diff --git a/source/benoit/benoit/render/render_row/julia.rs b/source/benoit/benoit/render/render_row/julia.rs index 7b7c2ef..9202a19 100644 --- a/source/benoit/benoit/render/render_row/julia.rs +++ b/source/benoit/benoit/render/render_row/julia.rs @@ -32,7 +32,7 @@ use rug::float::Special; use std::sync::Arc; pub fn julia(data: Arc<RenderData>, y: u32, iterator: IteratorFunction) { - let (iter_count_buffer, square_dist_buffer) = data.output(y); + let (iter_count_buffer, square_dist_buffer) = data.output_buffers(y); let (canvas_width, centre_real, centre_imag, _zoom, max_iter_count) = data.input(); diff --git a/source/benoit/benoit/render/render_row/normal.rs b/source/benoit/benoit/render/render_row/normal.rs index b39af7c..6e310d7 100644 --- a/source/benoit/benoit/render/render_row/normal.rs +++ b/source/benoit/benoit/render/render_row/normal.rs @@ -32,7 +32,7 @@ use rug::float::Special; use std::sync::Arc; pub fn normal(data: Arc<RenderData>, y: u32, iterator: IteratorFunction) { - let (iter_count_buffer, square_dist_buffer) = data.output(y); + let (iter_count_buffer, square_dist_buffer) = data.output_buffers(y); let (canvas_width, centre_real, centre_imag, zoom, max_iter_count) = data.input(); |