diff options
55 files changed, 2021 insertions, 813 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index 92142bc..c09d5b4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,18 @@ +# 2.0.0 + +* Bump major version +* Update controls +* Support non-square canvasses again +* Modulise and refactor code +* Draw textual feedback to window (enable with F1) +* Bump dependency versions +* Support multiple palette functions (reflect in configuration) +* Add multibrot3 fractal +* Improve commenting +* Check interactive input +* Remove dumping from interactive mode +* Fix image file extensions + # 1.2.1 * Fix readme @@ -1,6 +1,6 @@ [package] name = "benoit" -version = "1.2.0" +version = "2.0.0" authors = ["Gabriel Bjørnager Jensen"] edition = "2021" description = "Mandelbrot renderer." @@ -17,8 +17,8 @@ lto = "fat" [dependencies] png = "0.17.10" -rayon = "1.7.0" +rayon = "1.8.0" rug = "1.22.0" sdl2 = "0.35.2" toml = "0.8.0" -webp = "0.2.5" +webp = "0.2.6" diff --git a/source/benoit/benoit.rs b/source/benoit/benoit.rs index df2d104..bbbc158 100644 --- a/source/benoit/benoit.rs +++ b/source/benoit/benoit.rs @@ -27,10 +27,15 @@ use rug::Float; pub mod app; pub mod configuration; +pub mod factorisation; pub mod fractal; -pub mod iteration; -pub mod task; +pub mod palette; +pub mod rendering; +pub mod render; pub mod video; +pub mod width_height_ratio; + +pub use width_height_ratio::*; pub struct Version<T> { major: T, @@ -39,13 +44,19 @@ pub struct Version<T> { } pub const VERSION: Version::<u32> = Version::<u32> { - major: 0x1, - minor: 0x2, - patch: 0x1, + major: 0x2, + minor: 0x0, + 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, @@ -56,7 +67,7 @@ pub struct FeedbackInfo<'a> { prev_centre_real: &'a Float, prev_centre_imag: &'a Float, prev_zoom: &'a Float, - next_centre_real: &'a Float, - next_centre_imag: &'a Float, - next_zoom: &'a Float, + next_centre_real: &'a Float, + next_centre_imag: &'a Float, + next_zoom: &'a Float, } diff --git a/source/benoit/benoit/app.rs b/source/benoit/benoit/app.rs index 255461e..1863a77 100644 --- a/source/benoit/benoit/app.rs +++ b/source/benoit/benoit/app.rs @@ -22,62 +22,68 @@ */ use crate::benoit::ImageFormat; +use crate::benoit::factorisation::Factorisation; use crate::benoit::fractal::Fractal; -use crate::benoit::iteration::IteratorFunction; -use crate::benoit::task::render_data::RenderData; +use crate::benoit::palette::Palette; +use crate::benoit::rendering::Rendering; +use crate::benoit::render::{FactoriserFunction, IteratorFunction, PaletteFunction, RowRenderer}; use crate::benoit::video::Video; extern crate rug; use rug::Float; -use std::sync::Arc; +pub mod allocate_buffers; pub mod animate; pub mod colour; pub mod colour_row; pub mod drop; pub mod dump; -pub mod get_iterator_function; -pub mod get_row_renderer; pub mod handle_keys; pub mod image_filename; pub mod initialise; -pub mod r#loop; +pub mod interactive; pub mod poll_events; -pub mod render_row_julia; -pub mod render_row_normal; pub mod render; pub mod run; - -pub type RowRenderer = fn(Arc<RenderData>, u32, IteratorFunction); +pub mod still; pub struct App { #[allow(dead_code)] thread_count: u32, - fractal: Fractal, - julia: bool, + fractal: Fractal, + rendering: Rendering, + + canvas_width: u32, + canvas_height: u32, + scale: u32, + frame_count: u32, - canvas_width: u32, - scale: u32, - frame_count: u32, + centre_real: Float, + centre_imag: Float, + zoom: Float, + + multibrot_exponent: f32, - centre_real: Float, - centre_imag: Float, - zoom: Float, max_iter_count: u32, - colour_range: f32, + factorisation: Factorisation, + palette: Palette, + colour_range: f32, dump_path: String, image_format: ImageFormat, video: Option<Video>, - interactive: bool, - do_render: bool, - do_dump: bool, + interactive: bool, + do_render: bool, + do_textual_feedback: bool, row_renderer: RowRenderer, iterator_function: IteratorFunction, + + factoriser: FactoriserFunction, + palette_function: PaletteFunction, } diff --git a/source/benoit/benoit/app/get_iterator_function.rs b/source/benoit/benoit/app/allocate_buffers.rs index 1454785..b8616d5 100644 --- a/source/benoit/benoit/app/get_iterator_function.rs +++ b/source/benoit/benoit/app/allocate_buffers.rs @@ -21,16 +21,17 @@ If not, see <https://www.gnu.org/licenses/>. */ -use crate::benoit::fractal::Fractal; use crate::benoit::app::App; -use crate::benoit::iteration::*; impl App { - pub fn get_iterator_function(fractal: Fractal) -> IteratorFunction { - return match fractal { - Fractal::BurningShip => iterate_burning_ship, - Fractal::Mandelbrot => iterate_mandelbrot, - Fractal::Tricorn => iterate_tricorn, - }; + 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; + + 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 (iter_count_buffer, square_dist_buffer, image); } } diff --git a/source/benoit/benoit/app/animate.rs b/source/benoit/benoit/app/animate.rs index 3de4a76..7cc3549 100644 --- a/source/benoit/benoit/app/animate.rs +++ b/source/benoit/benoit/app/animate.rs @@ -33,37 +33,9 @@ use std::time::Instant; impl App { pub fn animate(&self) -> i32 { - let canvas_size = self.canvas_width as usize * self.canvas_width as usize; + assert!(self.frame_count > 0x1); - let mut iter_count_buffer: Vec::<u32> = vec![0x0; canvas_size]; - let mut square_dist_buffer: Vec::<f32> = vec![0.0; canvas_size]; - - let mut image: Vec::<u8> = vec![0x0; canvas_size * 0x3]; - - if self.frame_count == 0x1 { - // If only a single frame is to be rendered, we - // aren't going to animate it. - - 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); - let render_time = time_start.elapsed(); - - eprint!(" {:.3}ms, colouring...", render_time.as_micros() as f32 / 1000.0); - - self.colour(&mut image[..], self.max_iter_count, &mut iter_count_buffer[..], &mut square_dist_buffer[..]); - let colour_time = time_start.elapsed() - render_time; - - eprint!(" {:.3}ms...", colour_time.as_micros() as f32 / 1000.0); - - self.dump(format!("{}/render.webp", self.dump_path), &image, self.canvas_width); - - eprintln!(" done"); - - return 0x0; - } + 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); @@ -107,22 +79,20 @@ impl App { 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); - let render_time = time_start.elapsed(); + let render_time = time_start.elapsed(); eprint!(" {:.3}ms, colouring...", render_time.as_micros() as f32 / 1000.0); - self.colour(&mut image[..], self.max_iter_count, &mut iter_count_buffer[..], &mut square_dist_buffer[..]); - let colour_time = time_start.elapsed() - render_time; + self.colour(&mut image[..], self.multibrot_exponent, self.max_iter_count, &mut iter_count_buffer[..], &mut square_dist_buffer[..]); + let colour_time = time_start.elapsed() - render_time; eprint!(" {:.3}ms...", colour_time.as_micros() as f32 / 1000.0); - self.dump(format!("{}/render.webp", self.dump_path), &image, self.canvas_width); + let path = App::image_filename(format!("{}/frame{frame:010}.webp", self.dump_path).as_str(), self.image_format); - eprintln!(" done"); - - self.dump(format!("{}/frame{frame:010}.webp", self.dump_path), &image, self.canvas_width); + self.dump(path.as_str(), &image, self.canvas_width, self.canvas_height); - eprintln!(" rend. {:.3}ms, col. {:.3}ms", render_time.as_micros() as f32 / 1000.0, colour_time.as_micros() as f32 / 1000.0); + eprintln!(" done"); zoom *= &zoom_factor; } diff --git a/source/benoit/benoit/app/colour.rs b/source/benoit/benoit/app/colour.rs index 3325b55..03093ac 100644 --- a/source/benoit/benoit/app/colour.rs +++ b/source/benoit/benoit/app/colour.rs @@ -22,7 +22,7 @@ */ use crate::benoit::app::App; -use crate::benoit::task::colour_data::ColourData; +use crate::benoit::render::colour_data::ColourData; extern crate rayon; @@ -30,11 +30,14 @@ use rayon::prelude::*; use std::sync::Arc; impl App { - pub fn colour(&self, buffer: &mut [u8], max_iter_count: u32, iter_count_buffer: &[u32], square_dist_buffer: &[f32]) { - let data = Arc::new(ColourData::new(buffer, self.canvas_width, max_iter_count, self.colour_range, iter_count_buffer, square_dist_buffer)); + 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)); - (0x0..self.canvas_width).into_par_iter().for_each(|row| { - App::colour_row(data.clone(), row as u32); + 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 index 0348499..a4baca7 100644 --- a/source/benoit/benoit/app/colour_row.rs +++ b/source/benoit/benoit/app/colour_row.rs @@ -22,13 +22,13 @@ */ use crate::benoit::app::App; -use crate::benoit::task::colour_data::ColourData; +use crate::benoit::render::{FactoriserFunction, PaletteFunction}; +use crate::benoit::render::colour_data::ColourData; -use std::hint::unreachable_unchecked; use std::sync::Arc; impl App { - pub fn colour_row(data: Arc<ColourData>, y: u32) { + 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 { @@ -37,50 +37,25 @@ impl App { let iter_count = unsafe { *iter_count_buffer.get_unchecked( x) }; let distance = unsafe { *square_dist_buffer.get_unchecked(x) }.sqrt(); - let factor = (iter_count as f32 + 1.0 - distance.log2().log2()) / data.colour_range % 1.0; - - let (red, green, blue) = if iter_count < data.max_iter_count { - hsv_to_rgb(factor, 7.0 / 8.0, 7.0 / 8.0) + let factor = if iter_count < data.max_iter_count { + factoriser(iter_count, distance, data.colour_range, data.exponent) } else { - (0.0, 0.0, 0.0) + 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 { - *image.get_unchecked_mut(x * 0x3) = red; - *image.get_unchecked_mut(x * 0x3 + 0x1) = green; - *image.get_unchecked_mut(x * 0x3 + 0x2) = blue; + let x = x * 0x3; + + *image.get_unchecked_mut(x) = red; + *image.get_unchecked_mut(x + 0x1) = green; + *image.get_unchecked_mut(x + 0x2) = blue; } } } } - -fn hsv_to_rgb(hue: f32, saturation: f32, value: f32) -> (f32, f32, f32) { - return if saturation <= 0.0 { - let value = value.min(1.0); - - (value, value, value) - } else { - let h = hue % 1.0 * 6.0; - let s = saturation.min(1.0); - let v = value.min(1.0); - - let f = h % 1.0; - let p = v * (1.0 - s); - let q = v * (1.0 - s * f); - let t = v * (1.0 - s * (1.0 - f)); - - match h.trunc() as u8 { - 0x0 => (v, t, p), - 0x1 => (q, v, p), - 0x2 => (p, v, t), - 0x3 => (p, q, v), - 0x4 => (t, p, v), - 0x5 => (v, p, q), - _ => unsafe { unreachable_unchecked() }, - } - }; -} diff --git a/source/benoit/benoit/app/dump.rs b/source/benoit/benoit/app/dump.rs index 094650a..a0fb3c5 100644 --- a/source/benoit/benoit/app/dump.rs +++ b/source/benoit/benoit/app/dump.rs @@ -31,19 +31,19 @@ use std::fs::{File, write}; use std::io::BufWriter; impl App { - pub fn dump(&self, path: String, image: &[u8], canvas_width: u32) { + pub fn dump(&self, path: &str, image: &[u8], canvas_width: u32, canvas_height: u32) { match self.image_format { - ImageFormat::Png => dump_png( &path, image, canvas_width), - ImageFormat::Webp => dump_webp(&path, image, canvas_width), + ImageFormat::Png => dump_png( path, image, canvas_width, canvas_height), + ImageFormat::Webp => dump_webp(path, image, canvas_width, canvas_height), } } } -fn dump_png(path: &String, image: &[u8], canvas_width: u32) { +fn dump_png(path: &str, image: &[u8], canvas_width: u32, canvas_height: u32) { 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_width); + 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); @@ -53,8 +53,8 @@ fn dump_png(path: &String, image: &[u8], canvas_width: u32) { writer.write_image_data(image).expect("unable to write image"); } -fn dump_webp(path: &String, image: &[u8], canvas_width: u32) { - let encoder = webp::Encoder::from_rgb(&image[..], canvas_width, canvas_width); +fn dump_webp(path: &str, image: &[u8], canvas_width: u32, canvas_height: u32) { + let encoder = webp::Encoder::from_rgb(&image[..], canvas_width, canvas_height); let data = encoder.encode_lossless(); diff --git a/source/benoit/benoit/app/handle_keys.rs b/source/benoit/benoit/app/handle_keys.rs index 50956f8..da88bfc 100644 --- a/source/benoit/benoit/app/handle_keys.rs +++ b/source/benoit/benoit/app/handle_keys.rs @@ -22,9 +22,12 @@ */ use crate::benoit::PRECISION; -use crate::benoit::app::{App, RowRenderer}; +use crate::benoit::app::App; +use crate::benoit::factorisation::Factorisation; use crate::benoit::fractal::Fractal; -use crate::benoit::iteration::IteratorFunction; +use crate::benoit::palette::Palette; +use crate::benoit::render::{FactoriserFunction, IteratorFunction, PaletteFunction, RowRenderer}; +use crate::benoit::rendering::Rendering; extern crate rug; extern crate sdl2; @@ -35,12 +38,16 @@ use sdl2::keyboard::Scancode; impl App { pub fn handle_keys(&mut self, scan_code: Scancode) -> bool { match scan_code { - Scancode::LAlt => (self.fractal, self.iterator_function) = cycle_fractal(self.fractal), - Scancode::Escape => return true, Scancode::C => self.do_render = true, - Scancode::Tab => (self.julia, self.row_renderer) = toggle_julia(self.julia), - Scancode::X => self.do_dump = true, - Scancode::Z => eprintln!("c = {}{:+}i -- {}x @ {} iter. (range: {:.3})", &self.centre_real, &self.centre_imag, &self.zoom, self.max_iter_count, self.colour_range), + Scancode::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::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::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), _ => {}, } @@ -48,7 +55,7 @@ impl App { self.max_iter_count = match scan_code { Scancode::F => self.max_iter_count * 0x2, - Scancode::R => self.max_iter_count / 0x2, + Scancode::R => (self.max_iter_count / 0x2).max(0x1), _ => self.max_iter_count, }; @@ -56,7 +63,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, + Scancode::Down => (self.colour_range / COLOUR_RANGE_FACTOR).max(2.0), _ => self.colour_range, }; @@ -94,29 +101,48 @@ impl App { } } -fn cycle_fractal(fractal: Fractal) -> (Fractal, IteratorFunction) { - let fractal = match fractal { - Fractal::BurningShip => Fractal::Mandelbrot, - Fractal::Mandelbrot => Fractal::Tricorn, - Fractal::Tricorn => Fractal::BurningShip, - }; +fn cycle_fractal(fractal: Fractal, distance: i8) -> (Fractal, f32, IteratorFunction) { + let fractal = fractal + distance; + let exponent = fractal.get_exponent(); - let iterator_function = App::get_iterator_function(fractal); + let iterator_function = fractal.get_iterator(); eprintln!("rendering the {}", fractal.get_name()); - return (fractal, iterator_function); + return (fractal, exponent, iterator_function); } -fn toggle_julia(julia: bool) -> (bool, RowRenderer) { - let julia = !julia; +fn toggle_julia(rendering: Rendering) -> (Rendering, RowRenderer) { + let rendering = rendering.cycle(); - let row_renderer = App::get_row_renderer(julia); + let row_renderer = rendering.get_row_renderer(); - match julia { - false => eprintln!("disabled the julia set"), - true => eprintln!("enabled the julia set"), + match rendering { + Rendering::Julia => eprintln!("enabled the julia set"), + Rendering::Normal => eprintln!("disabled the julia set"), }; - return (julia, row_renderer); + 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(); + + eprintln!("using palette \"{}\"", palette.get_name()); + + return (palette, palette_function); } diff --git a/source/benoit/benoit/app/initialise.rs b/source/benoit/benoit/app/initialise.rs index a357940..6a4d385 100644 --- a/source/benoit/benoit/app/initialise.rs +++ b/source/benoit/benoit/app/initialise.rs @@ -52,42 +52,49 @@ impl App { eprintln!("using {thread_count} threads"); let video = match configuration.interactive { - true => Some(Video::initialise(configuration.canvas_width, configuration.scale)), + 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(); - eprintln!("rendering the {}", configuration.fractal.get_name()); - return App { thread_count: thread_count, - fractal: configuration.fractal, - julia: configuration.julia, + fractal: configuration.fractal, + rendering: configuration.rendering, + + canvas_width: configuration.canvas_width, + canvas_height: configuration.canvas_height, + scale: configuration.scale, + frame_count: configuration.frame_count, - canvas_width: configuration.canvas_width, - scale: configuration.scale, - frame_count: configuration.frame_count, + centre_real: configuration.centre_real, + centre_imag: configuration.centre_imag, + zoom: configuration.zoom, + + multibrot_exponent: 2.0, - centre_real: configuration.centre_real, - centre_imag: configuration.centre_imag, - zoom: configuration.zoom, max_iter_count: configuration.max_iter_count, - colour_range: configuration.colour_range, + factorisation: configuration.factorisation, + palette: configuration.palette, + colour_range: configuration.colour_range, dump_path: configuration.dump_path, image_format: configuration.image_format, video: video, - interactive: configuration.interactive, - do_render: true, - do_dump: false, + interactive: configuration.interactive, + do_render: true, + do_textual_feedback: false, + + row_renderer: configuration.rendering.get_row_renderer(), + iterator_function: configuration.fractal.get_iterator(), - row_renderer: App::get_row_renderer( configuration.julia), - iterator_function: App::get_iterator_function(configuration.fractal), + 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 new file mode 100644 index 0000000..f03576d --- /dev/null +++ b/source/benoit/benoit/app/interactive.rs @@ -0,0 +1,152 @@ +/* + 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::FeedbackInfo; +use crate::benoit::app::App; +use crate::benoit::rendering::Rendering; +use crate::benoit::video::Video; + +extern crate rug; + +use rug::{Assign, Float}; +use std::time::Instant; + +impl App { + 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.multibrot_exponent; + + // Used for translation feedback: + let mut prev_centre_real = self.centre_real.clone(); + let mut prev_centre_imag = self.centre_imag.clone(); + let mut prev_zoom = self.zoom.clone(); + + loop { + let frame_start = Instant::now(); + + if self.poll_events(&mut event_pump) { break } + + if self.do_render { + eprint!("rendering..."); + + 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); + let render_time = time_start.elapsed(); + + eprintln!(" {:.3}ms", render_time.as_micros() as f32 / 1000.0); + + prev_max_iter_count = self.max_iter_count; + prev_multibrot_exponent = self.multibrot_exponent; + + prev_centre_real.assign(&self.centre_real); + prev_centre_imag.assign(&self.centre_imag); + prev_zoom.assign(&self.zoom); + + 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[..]); + + video.draw(&image[..], self.canvas_width, self.canvas_height, self.scale); + self.draw_feedback(&mut video, &prev_centre_real, &prev_centre_imag, &prev_zoom); + + video.update(); + + video.sync(&frame_start); + } + + return 0x0; + } + + pub fn draw_feedback(&self, video: &mut Video, prev_centre_real: &Float, prev_centre_imag: &Float, prev_zoom: &Float) { + let julia = match self.rendering { + Rendering::Julia => true, + _ => false, + }; + + if { + // Don't draw translation feedback if rendering a + // Julia set or if we haven't done any viewport + // translations. + + !julia + && (&self.centre_real != prev_centre_real + || &self.centre_imag != prev_centre_imag + || &self.zoom != prev_zoom) + }{ + let feedback_info = FeedbackInfo { + prev_centre_real: prev_centre_real, + prev_centre_imag: prev_centre_imag, + prev_zoom: prev_zoom, + next_centre_real: &self.centre_real, + next_centre_imag: &self.centre_imag, + next_zoom: &self.zoom, + }; + + video.draw_translation_feedback(self.canvas_width, self.canvas_height, self.scale, &feedback_info); + } + + if self.do_textual_feedback { video.draw_textual_feedback(&self.centre_real, &self.centre_imag, &self.zoom, self.max_iter_count) }; + } + + pub fn print_controls() { + println!("Controls:"); + println!("- \u{1B}[1mW\u{1B}[0m Translate up"); + println!("- \u{1B}[1mA\u{1B}[0m Translate left"); + println!("- \u{1B}[1mS\u{1B}[0m Translate down"); + println!("- \u{1B}[1mD\u{1B}[0m Translate right"); + println!(); + 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}[1mTab\u{1B}[0m Toggle Julia"); + 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!(); + println!("- \u{1B}[1mUp\u{1B}[0m Increase colour range"); + println!("- \u{1B}[1mDown\u{1B}[0m Decrease colour range"); + println!(); + println!("- \u{1B}[1mLeft\u{1B}[0m Cycle to previous palette"); + 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!(); + println!("- \u{1B}[1mSpace\u{1B}[0m Render frame"); + println!(); + } +}
\ No newline at end of file diff --git a/source/benoit/benoit/app/loop.rs b/source/benoit/benoit/app/loop.rs deleted file mode 100644 index 69eb3d4..0000000 --- a/source/benoit/benoit/app/loop.rs +++ /dev/null @@ -1,140 +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::FeedbackInfo; -use crate::benoit::app::App; - -extern crate rug; - -use rug::Assign; -use std::time::Instant; - -impl App { - pub fn r#loop(&mut self) -> i32 { - assert_eq!(self.video.is_some(), true); - - eprintln!(); - eprintln!("Controls:"); - eprintln!("- W Translate up"); - eprintln!("- A Translate left"); - eprintln!("- S Translate down"); - eprintln!("- D Translate right"); - eprintln!(); - eprintln!("- Q Zoom out"); - eprintln!("- E Zoom in"); - eprintln!(); - eprintln!("- R Decrease max. iteration count"); - eprintln!("- F Increase max. iteration count"); - eprintln!(); - eprintln!("- Tab Toggle Julia"); - eprintln!("- Alt Cycle between fractals"); - eprintln!(); - eprintln!("- Up Increase colour range"); - eprintln!("- Down Decrease colour range"); - eprintln!(); - eprintln!("- Z Print centre value (c)"); - eprintln!("- X Dump frame"); - eprintln!("- C Render frame"); - eprintln!(); - - let mut event_pump = unsafe { self.video.as_mut().unwrap_unchecked().sdl.event_pump().expect("unable to get event pump") }; - - let canvas_size = self.canvas_width as usize * self.canvas_width as usize; - - let mut iter_count_buffer: Vec::<u32> = vec![0x0; canvas_size]; - let mut square_dist_buffer: Vec::<f32> = vec![0.0; canvas_size]; - - let mut image: Vec::<u8> = vec![0x0; canvas_size * 0x3]; - - let mut prev_centre_real = self.centre_real.clone(); - let mut prev_centre_imag = self.centre_imag.clone(); - let mut prev_zoom = self.zoom.clone(); - let mut prev_max_iter_count = self.max_iter_count; - - loop { - let frame_start = Instant::now(); - - if self.poll_events(&mut event_pump) { break } - - if self.do_render { - eprint!("rendering..."); - - 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); - let render_time = time_start.elapsed(); - - eprintln!(" {:.3}ms", render_time.as_micros() as f32 / 1000.0); - - prev_centre_real.assign(&self.centre_real); - prev_centre_imag.assign(&self.centre_imag); - prev_zoom.assign(&self.zoom); - prev_max_iter_count = self.max_iter_count; - - self.do_render = false; - } - - self.colour(&mut image[..], prev_max_iter_count.min(self.max_iter_count), &mut iter_count_buffer[..], &mut square_dist_buffer[..]); - - { - let feedback_info = FeedbackInfo { - prev_centre_real: &prev_centre_real, - prev_centre_imag: &prev_centre_imag, - prev_zoom: &prev_zoom, - next_centre_real: &self.centre_real, - next_centre_imag: &self.centre_imag, - next_zoom: &self.zoom, - }; - - let feedback_info = if { - // Don't draw feedback if rendering a Julia set or - // if we haven't done any viewport translations. - - !self.julia - && (self.centre_real != prev_centre_real - || self.centre_imag != prev_centre_imag - || self.zoom != prev_zoom) - } { - Some(&feedback_info) - } else { - None - }; - - unsafe { self.video.as_mut().unwrap_unchecked().draw(&image[..], self.canvas_width, self.scale, feedback_info) }; - } - - if self.do_dump { - let path = App::image_filename(format!("{}/image", self.dump_path).as_str(), self.image_format); - - eprintln!("dumping image at \"{path}\""); - self.dump(path, &image, self.canvas_width); - - self.do_dump = false; - } - - unsafe { self.video.as_ref().unwrap_unchecked().sync(&frame_start) }; - } - - return 0x0; - } -}
\ 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 2df0c96..f5e38b6 100644 --- a/source/benoit/benoit/app/poll_events.rs +++ b/source/benoit/benoit/app/poll_events.rs @@ -44,7 +44,7 @@ impl App { _ => false, }; - if quit { return true } + if quit { return true }; } return false; diff --git a/source/benoit/benoit/app/render.rs b/source/benoit/benoit/app/render.rs index aa5ff9b..6e4a382 100644 --- a/source/benoit/benoit/app/render.rs +++ b/source/benoit/benoit/app/render.rs @@ -22,7 +22,7 @@ */ use crate::benoit::app::App; -use crate::benoit::task::render_data::RenderData; +use crate::benoit::render::render_data::RenderData; extern crate rayon; extern crate rug; @@ -36,9 +36,9 @@ impl App { let row_renderer = self.row_renderer; let iterator = self.iterator_function; - let data = Arc::new(RenderData::new(iter_count_buffer, square_dist_buffer, self.canvas_width, 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_width).into_par_iter().for_each(|row| { + (0x0..self.canvas_height).into_par_iter().for_each(|row| { row_renderer(data.clone(), row as u32, iterator); }); } diff --git a/source/benoit/benoit/app/render_row_julia.rs b/source/benoit/benoit/app/render_row_julia.rs deleted file mode 100644 index 2710423..0000000 --- a/source/benoit/benoit/app/render_row_julia.rs +++ /dev/null @@ -1,100 +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::PRECISION; -use crate::benoit::app::App; -use crate::benoit::iteration::IteratorFunction; -use crate::benoit::task::render_data::RenderData; - -extern crate rug; - -use rug::{Assign, Float}; -use rug::float::Special; -use rug::ops::NegAssign; -use std::sync::Arc; - -impl App { - pub fn render_row_julia(data: Arc<RenderData>, y: u32, iterator: IteratorFunction) { - let (iter_count_buffer, square_dist_buffer) = unsafe { data.slice(y) }; - - for x in 0x0..data.canvas_width { - let canvas_width = Float::with_val(PRECISION, data.canvas_width); - - // For more information, see render_row_normal. - - let ca = &data.centre_real; - let cb = &data.centre_imag; - - // When rendering the Julia fractals, the value of - // (c) remains constant throughout the entire - // canvas. The value of (z) - however - takes the - // position-determined value that (c) would've had. - - let mut za = { - let mut za = Float::with_val(PRECISION, &canvas_width / 2.0); - za.neg_assign(); - za += x; - za *= 4.0; - za /= &canvas_width; - - za - }; - - let mut zb = { - let mut zb = Float::with_val(PRECISION, &canvas_width / 2.0); - zb.neg_assign(); - zb += y; - zb *= 4.0; - zb /= &canvas_width; - - zb - }; - - let mut za_prev = Float::with_val(PRECISION, Special::Nan); - let mut zb_prev = Float::with_val(PRECISION, Special::Nan); - - let mut iter_count: u32 = 0x0; - let mut square_dist; - while { - square_dist = Float::with_val(PRECISION, &za * &za + &zb * &zb).to_f32(); - - let periodic = za == za_prev && zb == zb_prev; - - if periodic { iter_count = data.max_iter_count } - square_dist <= 256.0 && iter_count < data.max_iter_count - } { - za_prev.assign(&za); - zb_prev.assign(&zb); - - iterator(&mut za, &mut zb, ca, cb); - - iter_count += 0x1; - } - - unsafe { - *iter_count_buffer.get_unchecked_mut( x as usize) = iter_count; - *square_dist_buffer.get_unchecked_mut(x as usize) = square_dist; - } - } - } -} diff --git a/source/benoit/benoit/app/render_row_normal.rs b/source/benoit/benoit/app/render_row_normal.rs deleted file mode 100644 index 832d178..0000000 --- a/source/benoit/benoit/app/render_row_normal.rs +++ /dev/null @@ -1,101 +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::PRECISION; -use crate::benoit::app::App; -use crate::benoit::iteration::IteratorFunction; -use crate::benoit::task::render_data::RenderData; - -extern crate rug; - -use rug::{Assign, Float}; -use rug::float::Special; -use rug::ops::NegAssign; -use std::sync::Arc; - -impl App { - pub fn render_row_normal(data: Arc<RenderData>, y: u32, iterator: IteratorFunction) { - let (iter_count_buffer, square_dist_buffer) = unsafe { data.slice(y) }; - - for x in 0x0..data.canvas_width { - let canvas_width = Float::with_val(PRECISION, data.canvas_width); - - let ca = { - let mut ca = Float::with_val(PRECISION, &canvas_width / 2.0); - ca.neg_assign(); - ca += x; - ca *= 4.0; - ca /= &canvas_width; - ca /= &data.zoom; - ca += &data.centre_real; - - ca - }; - - let cb = { - let mut cb = Float::with_val(PRECISION, &canvas_width / 2.0); - cb.neg_assign(); - cb += y; - cb *= 4.0; - cb /= &canvas_width; - cb /= &data.zoom; - cb -= &data.centre_imag; - - cb - }; - - let mut za = ca.clone(); - let mut zb = cb.clone(); - - let mut za_prev = Float::with_val(PRECISION, Special::Nan); - let mut zb_prev = Float::with_val(PRECISION, Special::Nan); - - let mut iter_count: u32 = 0x1; - let mut square_dist; - while { - square_dist = Float::with_val(PRECISION, &za * &za + &zb * &zb).to_f32(); - // Having a larger escape radius gives better - // results with regard to smoothing. - - // Check if the value is periodic, i.e. its - // sequence repeats. - let periodic = za == za_prev && zb == zb_prev; - - if periodic { iter_count = data.max_iter_count } - square_dist <= 256.0 && iter_count < data.max_iter_count - } { - za_prev.assign(&za); - zb_prev.assign(&zb); - - iterator(&mut za, &mut zb, &ca, &cb); - - iter_count += 0x1; - } - - unsafe { - *iter_count_buffer.get_unchecked_mut( x as usize) = iter_count; - *square_dist_buffer.get_unchecked_mut(x as usize) = square_dist; - } - } - } -} diff --git a/source/benoit/benoit/app/run.rs b/source/benoit/benoit/app/run.rs index 3524cad..9211716 100644 --- a/source/benoit/benoit/app/run.rs +++ b/source/benoit/benoit/app/run.rs @@ -29,13 +29,18 @@ extern crate sdl2; impl App { pub fn run(&mut self) -> i32 { println!(); - println!("Benoit {:X}.{:X}.{:X}", VERSION.major, VERSION.minor, VERSION.patch); - println!("Copyright 2021, 2023 Gabriel Bjørnager Jensen."); + println!("\u{1B}[1mBENO\u{CE}T\u{1B}[0m {:X}.{:X}.{:X}", VERSION.major, VERSION.minor, VERSION.patch); + println!("Copyright 2021, 2023 Gabriel Bj\u{F8}rnager Jensen."); + println!(); + println!("COCITAVIT\u{B7}ERCO\u{B7}FVIT"); println!(); return match self.interactive { - true => self.r#loop(), - false => self.animate(), + true => self.interactive(), + false => match self.frame_count { + 0x1 => self.still(), + _ => self.animate(), + }, }; } } diff --git a/source/benoit/benoit/app/still.rs b/source/benoit/benoit/app/still.rs new file mode 100644 index 0000000..bd5ea9e --- /dev/null +++ b/source/benoit/benoit/app/still.rs @@ -0,0 +1,56 @@ +/* + 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; + +extern crate rug; + +use std::time::Instant; + +impl App { + pub fn still(&self) -> i32 { + assert_eq!(self.frame_count, 0x1); + + let (mut iter_count_buffer, mut square_dist_buffer, mut image) = App::allocate_buffers(self.canvas_width, self.canvas_height); + + eprint!("rendering at {}{:+}i ({}x)...", self.centre_real.to_f32(), self.centre_imag.to_f32(), self.zoom.to_f32()); + let time_start = Instant::now(); + + self.render(&mut iter_count_buffer[..], &mut square_dist_buffer[..], &self.centre_real, &self.centre_imag, &self.zoom, self.max_iter_count); + 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[..]); + let colour_time = time_start.elapsed() - render_time; + + eprint!(" {:.3}ms...", colour_time.as_micros() as f32 / 1000.0); + + let path = App::image_filename(format!("{}/render.webp", self.dump_path).as_str(), self.image_format); + self.dump(path.as_str(), &image, self.canvas_width, self.canvas_height); + + eprintln!(" done"); + + return 0x0; + } +} diff --git a/source/benoit/benoit/configuration.rs b/source/benoit/benoit/configuration.rs index 6e22f01..828ccae 100644 --- a/source/benoit/benoit/configuration.rs +++ b/source/benoit/benoit/configuration.rs @@ -22,7 +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; extern crate rug; @@ -34,19 +37,23 @@ pub mod load; pub struct Configuration { pub thread_count: u32, - pub fractal: Fractal, - pub julia: bool, + pub fractal: Fractal, + pub rendering: Rendering, - pub canvas_width: u32, - pub scale: u32, - pub frame_count: u32, + pub canvas_width: u32, + pub canvas_height: u32, + pub scale: u32, + pub frame_count: u32, + + pub centre_real: Float, + pub centre_imag: Float, + pub zoom: Float, - pub centre_real: Float, - pub centre_imag: Float, - pub zoom: Float, pub max_iter_count: u32, - pub colour_range: f32, + pub factorisation: Factorisation, + pub palette: Palette, + pub colour_range: f32, pub dump_path: String, pub image_format: ImageFormat, diff --git a/source/benoit/benoit/configuration/default.rs b/source/benoit/benoit/configuration/default.rs index 2a0ae9f..f7f30d7 100644 --- a/source/benoit/benoit/configuration/default.rs +++ b/source/benoit/benoit/configuration/default.rs @@ -22,8 +22,11 @@ */ use crate::benoit::{ImageFormat, PRECISION}; -use crate::benoit::configuration::Configuration; +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; @@ -34,21 +37,25 @@ impl Configuration { return Configuration { thread_count: 0x0, - fractal: Fractal::Mandelbrot, - julia: false, + fractal: Fractal::Mandelbrot, + rendering: Rendering::Normal, + + canvas_width: 0x100, + canvas_height: 0xC0, + scale: 0x2, + frame_count: 0x10, - canvas_width: 0x100, - 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), - 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, - colour_range: 64.0, + factorisation: Factorisation::Smooth, + palette: Palette::Fire, + colour_range: 64.0, - dump_path: "./render/".to_string(), + 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 cd4b812..0b4b20f 100644 --- a/source/benoit/benoit/configuration/load.rs +++ b/source/benoit/benoit/configuration/load.rs @@ -22,8 +22,11 @@ */ use crate::benoit::{ImageFormat, PRECISION}; -use crate::benoit::configuration::Configuration; +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; @@ -48,78 +51,57 @@ impl Configuration { let configuration_table = Table::from_str(configuration_text.as_str()).expect("unable to parse configuration"); - let get_boolean = |buffer: &mut bool, table: &Table, name: &str| { - if !table.contains_key(name) { return } - - match &configuration_table[name] { - Value::Boolean(value) => *buffer = *value, - _ => panic!("mismatched type of {name}"), - }; - }; - - let get_integer = |buffer: &mut u32, table: &Table, name: &str| { - if !table.contains_key(name) { return } - - match &configuration_table[name] { - Value::Integer(value) => *buffer = *value as u32, - _ => panic!("mismatched type of {name}"), - }; - }; - - let get_float32 = |buffer: &mut f32, table: &Table, name: &str| { - if !table.contains_key(name) { return } - - match &configuration_table[name] { - Value::Float(value) => *buffer = *value as f32, - _ => panic!("mismatched type of {name}"), - }; - }; - - let get_float = |buffer: &mut Float, table: &Table, name: &str| { - if !table.contains_key(name) { return } - - match &configuration_table[name] { - Value::String(string) => { - *buffer = match Float::parse(string) { - Ok(value) => Float::with_val(PRECISION, value), - _ => panic!("invalid format of {name}"), - } - }, - _ => panic!("mismatched type of {name}"), - }; - }; - - let get_string = |table: &Table, name: &str| -> Option<&String> { - if !table.contains_key(name) { return None } - - match &configuration_table[name] { - Value::String(value) => return Some(value), - _ => panic!("mismatched type of {name}"), - }; - }; - 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 name \"{name}\""), + name => panic!("invalid fractal kind \"{name}\""), } } - get_boolean(&mut configuration.julia, &configuration_table, "julia"); + 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_integer(&mut configuration.canvas_width, &configuration_table, "canvas_width"); - 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_float( &mut configuration.centre_real, &configuration_table, "real"); - get_float( &mut configuration.centre_imag, &configuration_table, "imaginary"); - get_float( &mut configuration.zoom, &configuration_table, "zoom"); get_integer(&mut configuration.max_iter_count, &configuration_table, "maximum_iteration_count"); - get_float32(&mut configuration.colour_range, &configuration_table, "colour_range"); + 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(); @@ -130,7 +112,7 @@ impl Configuration { "png" => ImageFormat::Png, "webp" => ImageFormat::Webp, name => panic!("invalid image format \"{name}\""), - } + }; } match check_configuration(&configuration) { @@ -157,3 +139,46 @@ fn check_configuration(configuration: &Configuration) -> Result<(), &str> { 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 new file mode 100644 index 0000000..fc0f7ba --- /dev/null +++ b/source/benoit/benoit/factorisation.rs @@ -0,0 +1,50 @@ +/* + 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/fractal.rs b/source/benoit/benoit/fractal.rs index 1c7c08d..5cefcfe 100644 --- a/source/benoit/benoit/fractal.rs +++ b/source/benoit/benoit/fractal.rs @@ -21,11 +21,69 @@ If not, see <https://www.gnu.org/licenses/>. */ -pub mod get_name; +use crate::benoit::render::IteratorFunction; +use crate::benoit::render::iterate; + +use std::mem::transmute; +use std::ops::Add; #[derive(Clone, Copy)] pub enum Fractal { BurningShip, Mandelbrot, + Multibrot3, Tricorn, } + +impl Fractal { + const MAX: u8 = Fractal::Tricorn as u8; + + pub fn get_name(self) -> &'static str { + return match self { + Fractal::BurningShip => "burning ship", + Fractal::Mandelbrot => "mandelbrot set", + Fractal::Multibrot3 => "multibrot (d=3)", + Fractal::Tricorn => "tricorn", + }; + } + + pub fn get_exponent(self) -> f32 { + return match self { + Fractal::BurningShip => 2.0, + Fractal::Mandelbrot => 2.0, + Fractal::Multibrot3 => 3.0, + Fractal::Tricorn => 2.0, + }; + } + + pub fn get_iterator(self) -> IteratorFunction { + return match self { + Fractal::BurningShip => iterate::burning_ship, + Fractal::Mandelbrot => iterate::mandelbrot, + Fractal::Multibrot3 => iterate::multibrot3, + Fractal::Tricorn => iterate::tricorn, + }; + } + +} + +impl Add<i8> for Fractal { + type Output = Fractal; + + fn add(self, direction: i8) -> Self { + assert!(direction != 0x0); + + let raw = self as i8 + direction; + let raw: u8 = if raw < 0x0 { + Fractal::MAX + } else if raw > Fractal::MAX as i8 { + 0x0 + } else { + raw as u8 + }; + + let new: Self = unsafe { transmute(raw) }; + + return new; + } +} diff --git a/source/benoit/benoit/palette.rs b/source/benoit/benoit/palette.rs new file mode 100644 index 0000000..628c5f4 --- /dev/null +++ b/source/benoit/benoit/palette.rs @@ -0,0 +1,85 @@ +/* + 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::PaletteFunction; +use crate::benoit::render::paint; + +use std::mem::transmute; +use std::ops::Add; + +#[derive(Clone, Copy)] +pub enum Palette { + Ancient, + Fire, + Greyscale, + Hsv, + Lch, + Sapphire, +} + +impl Palette { + const MAX: u8 = Palette::Sapphire as u8; + + pub fn get_name(self) -> &'static str { + return match self { + Palette::Ancient => "ancient", + Palette::Fire => "fire", + Palette::Greyscale => "greyscale", + Palette::Hsv => "hsv", + Palette::Lch => "lch", + Palette::Sapphire => "sapphire", + }; + } + + pub fn get_function(self) -> PaletteFunction { + return match self { + Palette::Ancient => paint::ancient, + Palette::Fire => paint::fire, + Palette::Greyscale => paint::greyscale, + Palette::Hsv => paint::hsv, + Palette::Lch => paint::lch, + 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; + } +} diff --git a/source/benoit/benoit/iteration.rs b/source/benoit/benoit/render.rs index 6c81b80..ddea2f6 100644 --- a/source/benoit/benoit/iteration.rs +++ b/source/benoit/benoit/render.rs @@ -21,16 +21,27 @@ If not, see <https://www.gnu.org/licenses/>. */ +use crate::benoit::render::render_data::RenderData; + extern crate rug; use rug::Float; +use std::sync::Arc; -pub mod iterate_burning_ship; -pub mod iterate_mandelbrot; -pub mod iterate_tricorn; - -pub use iterate_burning_ship::iterate_burning_ship; -pub use iterate_mandelbrot::iterate_mandelbrot; -pub use iterate_tricorn::iterate_tricorn; +pub mod colour_data; +pub mod factorise; +pub mod iterate; +pub mod paint; +pub mod render_data; +pub mod render_row; 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/task/colour_data/slice.rs b/source/benoit/benoit/render/colour_data.rs index 975eda2..1d8e278 100644 --- a/source/benoit/benoit/task/colour_data/slice.rs +++ b/source/benoit/benoit/render/colour_data.rs @@ -21,11 +21,37 @@ If not, see <https://www.gnu.org/licenses/>. */ -use crate::benoit::task::colour_data::ColourData; - use std::slice::{from_raw_parts, from_raw_parts_mut}; +pub struct ColourData { + pub canvas_width: u32, + + pub exponent: f32, + pub max_iter_count: u32, + pub colour_range: f32, + + iter_count_buffer: *const u32, + square_dist_buffer: *const f32, + + image: *mut u8, +} + 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 { + return ColourData { + canvas_width: canvas_width, + + exponent: exponent, + max_iter_count: max_iter_count, + colour_range: colour_range, + + iter_count_buffer: iter_count_buffer.as_ptr(), + square_dist_buffer: square_dist_buffer.as_ptr(), + + image: image.as_mut_ptr(), + }; + } + pub unsafe fn slice(&self, row: u32) -> (&[u32], &[f32], &mut [u8]) { let offset = row as isize * self.canvas_width as isize; @@ -39,3 +65,6 @@ impl ColourData { return (iter_count, dist, image); } } + +unsafe impl Send for ColourData {} +unsafe impl Sync for ColourData {} diff --git a/source/benoit/benoit/render/factorise.rs b/source/benoit/benoit/render/factorise.rs new file mode 100644 index 0000000..ab2f75e --- /dev/null +++ b/source/benoit/benoit/render/factorise.rs @@ -0,0 +1,28 @@ +/* + 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/task/colour_data.rs b/source/benoit/benoit/render/factorise/smooth.rs index 77d561d..b324af5 100644 --- a/source/benoit/benoit/task/colour_data.rs +++ b/source/benoit/benoit/render/factorise/smooth.rs @@ -21,20 +21,10 @@ If not, see <https://www.gnu.org/licenses/>. */ -pub mod new; -pub mod slice; +// Classic colour palette, fixed from version ↋. -pub struct ColourData { - pub canvas_width: u32, +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; - pub max_iter_count: u32, - pub colour_range: f32, - - iter_count_buffer: *const u32, - square_dist_buffer: *const f32, - - image: *mut u8, + return factor; } - -unsafe impl Send for ColourData {} -unsafe impl Sync for ColourData {} diff --git a/source/benoit/benoit/fractal/get_name.rs b/source/benoit/benoit/render/factorise/stepped.rs index cdbadb1..df9ac2d 100644 --- a/source/benoit/benoit/fractal/get_name.rs +++ b/source/benoit/benoit/render/factorise/stepped.rs @@ -21,14 +21,11 @@ If not, see <https://www.gnu.org/licenses/>. */ -use crate::benoit::fractal::Fractal; +// Classic colour palette, fixed (and smoothed) +// from version ↋. -impl Fractal { - pub fn get_name(self) -> &'static str { - return match self { - Fractal::BurningShip => "burning ship", - Fractal::Mandelbrot => "mandelbrot set", - Fractal::Tricorn => "tricorn", - }; - } +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/render/iterate.rs b/source/benoit/benoit/render/iterate.rs new file mode 100644 index 0000000..723da26 --- /dev/null +++ b/source/benoit/benoit/render/iterate.rs @@ -0,0 +1,32 @@ +/* + 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 burning_ship; +pub mod mandelbrot; +pub mod multibrot3; +pub mod tricorn; + +pub use burning_ship::*; +pub use mandelbrot::*; +pub use multibrot3::*; +pub use tricorn::*; diff --git a/source/benoit/benoit/iteration/iterate_burning_ship.rs b/source/benoit/benoit/render/iterate/burning_ship.rs index 038367f..43e6916 100644 --- a/source/benoit/benoit/iteration/iterate_burning_ship.rs +++ b/source/benoit/benoit/render/iterate/burning_ship.rs @@ -25,7 +25,7 @@ extern crate rug; use rug::Float; -pub fn iterate_burning_ship(za: &mut Float, zb: &mut Float, ca: &Float, cb: &Float) { +pub fn burning_ship(za: &mut Float, zb: &mut Float, ca: &Float, cb: &Float) { // The Burning Ship is different in that - during // iteration - the real and imaginary parts of (z) // are made absolute: diff --git a/source/benoit/benoit/iteration/iterate_mandelbrot.rs b/source/benoit/benoit/render/iterate/mandelbrot.rs index 9037326..015e2e7 100644 --- a/source/benoit/benoit/iteration/iterate_mandelbrot.rs +++ b/source/benoit/benoit/render/iterate/mandelbrot.rs @@ -25,7 +25,7 @@ extern crate rug; use rug::Float; -pub fn iterate_mandelbrot(za: &mut Float, zb: &mut Float, ca: &Float, cb: &Float) { +pub fn mandelbrot(za: &mut Float, zb: &mut Float, ca: &Float, cb: &Float) { // The Mandelbrot Set (M) is defined as the set of // values in the complex plane where the iterating // function @@ -36,18 +36,21 @@ pub fn iterate_mandelbrot(za: &mut Float, zb: &mut Float, ca: &Float, cb: &Float // // abs(z) = sqrt(Re(z)^2+Im(z)^2) <= 2^2 = 4. - let za_temporary = za.clone(); + let za_temporary = za.clone(); // a // We can calculate the square of a complex number // as: // - // (a+bi)^2 = (a+bi)(a+bi) = a^2+abi+abi-b^2 = a^2-b^2+2abi. - - za.square_mut(); - *za -= &*zb * &*zb; - *za += ca; - - *zb *= za_temporary; - *zb *= 2.0; - *zb += cb; + // (a+bi)^2 + // = (a+bi)(a+bi) + // = a^2+abi+abi-b^2 + // = a^2-b^2+2abi. + + za.square_mut(); // a^2 + *za -= &*zb * &*zb; // a^2-b^2 + *za += ca; // a^2-b^2+Re(c) + + *zb *= za_temporary; // ab + *zb *= 2.0; // 2ab + *zb += cb; // 2ab+Im(c) } diff --git a/source/benoit/benoit/render/iterate/multibrot3.rs b/source/benoit/benoit/render/iterate/multibrot3.rs new file mode 100644 index 0000000..1de2e07 --- /dev/null +++ b/source/benoit/benoit/render/iterate/multibrot3.rs @@ -0,0 +1,60 @@ +/* + 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; + +extern crate rug; + +use rug::Float; + +pub fn multibrot3(za: &mut Float, zb: &mut Float, ca: &Float, cb: &Float) { + let za_temporary = za.clone(); // a + + // (a+bi)^3 + // = (a+bi)(a+bi)(a+bi) + // = (a^2-b^2+2abi)(a+bi) + // = 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 + + let mut tmp0 = Float::with_val(PRECISION, &*zb * &*zb); // b^2 + + let tmp1 = Float::with_val(PRECISION, &tmp0 * &*zb); // b^3 + + tmp0 *= &*za; // ab^2 + tmp0 *= 0x3; // 3ab^2 + + za.square_mut(); // a^2 + + *zb *= &*za; // (a^2)b + + *za *= &za_temporary; // a^3 + *za -= &tmp0; // a^3-3ab^2 + *za += ca; // a^3-3ab^2+Re(c) + + *zb *= 3.0; // 3(a^2)b + *zb -= &tmp1; // 3(a^2)b-b^3 + *zb += cb; // 3(a^2)b-b^3+Im(c) +} diff --git a/source/benoit/benoit/iteration/iterate_tricorn.rs b/source/benoit/benoit/render/iterate/tricorn.rs index 3d1bfb9..2f11ecf 100644 --- a/source/benoit/benoit/iteration/iterate_tricorn.rs +++ b/source/benoit/benoit/render/iterate/tricorn.rs @@ -25,18 +25,18 @@ extern crate rug; use rug::Float; -pub fn iterate_tricorn(za: &mut Float, zb: &mut Float, ca: &Float, cb: &Float) { +pub fn tricorn(za: &mut Float, zb: &mut Float, ca: &Float, cb: &Float) { // The Tricorn is only different from the // Mandelbrot Set in that the conjugate of (z) is // used instead of just (z): // - // z(n+1) = (Re(z(n))-Im(z(n)))^2+c. + // z(n+1) = (Re(z(n))-Im(z(n))i)^2+c. - let za_temporary = za.clone(); + let za_temporary = za.clone(); // a - za.square_mut(); - *za -= &*zb * &*zb; - *za += ca; + za.square_mut(); // a^2 + *za -= &*zb * &*zb; // a^2-b^2 + *za += ca; // a^2 *zb *= za_temporary; // We can negate the value by multiplying with diff --git a/source/benoit/benoit/render/paint.rs b/source/benoit/benoit/render/paint.rs new file mode 100644 index 0000000..ff3bd47 --- /dev/null +++ b/source/benoit/benoit/render/paint.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/>. +*/ + +pub mod ancient; +pub mod fire; +pub mod greyscale; +pub mod hsv; +pub mod lch; +pub mod sapphire; + +pub use ancient::*; +pub use fire::*; +pub use greyscale::*; +pub use hsv::*; +pub use lch::*; +pub use sapphire::*; diff --git a/source/benoit/benoit/render/paint/ancient.rs b/source/benoit/benoit/render/paint/ancient.rs new file mode 100644 index 0000000..a2c01eb --- /dev/null +++ b/source/benoit/benoit/render/paint/ancient.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/>. +*/ + +// Palette function from mandelbrotsdl, my first +// Mandelbrot renderer. + +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) + }; + + return (red, green, blue); +} diff --git a/source/benoit/benoit/task/render_data/new.rs b/source/benoit/benoit/render/paint/fire.rs index d9509b5..23ab2b0 100644 --- a/source/benoit/benoit/task/render_data/new.rs +++ b/source/benoit/benoit/render/paint/fire.rs @@ -21,25 +21,31 @@ If not, see <https://www.gnu.org/licenses/>. */ -use crate::benoit::task::render_data::RenderData; +// Classic colour palette, fixed (and smoothed) +// from version ↋. -extern crate rug; +pub fn fire(factor: f32) -> (f32, f32, f32) { + let factor = factor % 1.0; -use rug::Float; + 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); -impl RenderData { - pub fn new(iter_count_buffer: &mut [u32], square_dist_buffer: &mut [f32], canvas_width: u32, centre_real: Float, centre_imag: Float, zoom: Float, max_iter_count: u32) -> RenderData { - return RenderData { - canvas_width: canvas_width, + (1.0, factor * 4.0, 0.0) + } else if factor <= 3.0 / 4.0 { + let factor = factor - (1.0 / 2.0); - centre_real: centre_real, - centre_imag: centre_imag, - zoom: zoom, + (1.0, 1.0, factor * 4.0) + } else { + let factor = 1.0 - factor; - max_iter_count: max_iter_count, + (factor * 4.0, factor * 4.0, factor * 4.0) + } + } else { + (0.0, 0.0, 0.0) + }; - iter_count_buffer: iter_count_buffer.as_mut_ptr(), - square_dist_buffer: square_dist_buffer.as_mut_ptr(), - }; - } + return (red, green, blue); } diff --git a/source/benoit/benoit/task/render_data.rs b/source/benoit/benoit/render/paint/greyscale.rs index e73b514..977edf1 100644 --- a/source/benoit/benoit/task/render_data.rs +++ b/source/benoit/benoit/render/paint/greyscale.rs @@ -21,25 +21,23 @@ If not, see <https://www.gnu.org/licenses/>. */ -extern crate rug; +// Original palette function after the Rust- +// rewrite (version 10). -use rug::Float; +pub fn greyscale(factor: f32) -> (f32, f32, f32) { + let factor = factor % 1.0; -pub mod new; -pub mod slice; + let factor = (if factor >= 1.0 / 2.0 { + 1.0 - factor + } else { + factor + }) * 2.0; -pub struct RenderData { - pub canvas_width: u32, + let (red, green, blue) = if !factor.is_nan() { + (factor, factor, factor) + } else { + (0.0, 0.0, 0.0) + }; - pub centre_real: Float, - pub centre_imag: Float, - pub zoom: Float, - - pub max_iter_count: u32, - - iter_count_buffer: *mut u32, - square_dist_buffer: *mut f32, + return (red, green, blue); } - -unsafe impl Send for RenderData {} -unsafe impl Sync for RenderData {} diff --git a/source/benoit/benoit/render/paint/hsv.rs b/source/benoit/benoit/render/paint/hsv.rs new file mode 100644 index 0000000..e6b414b --- /dev/null +++ b/source/benoit/benoit/render/paint/hsv.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 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); +} + +fn hsv_to_rgb(hue: f32, saturation: f32, value: f32) -> (f32, f32, f32) { + return if saturation <= 0.0 { + let value = value.min(1.0); + + (value, value, value) + } else { + let h = hue % 1.0 * 6.0; + let s = saturation.min(1.0); + let v = value.min(1.0); + + let f = h % 1.0; + let p = v * (1.0 - s); + let q = v * (1.0 - s * f); + let t = v * (1.0 - s * (1.0 - f)); + + match h.trunc() as u8 { + 0x0 => (v, t, p), + 0x1 => (q, v, p), + 0x2 => (p, v, t), + 0x3 => (p, q, v), + 0x4 => (t, p, v), + 0x5 => (v, p, q), + _ => unsafe { unreachable_unchecked() }, + } + }; +} diff --git a/source/benoit/benoit/render/paint/lch.rs b/source/benoit/benoit/render/paint/lch.rs new file mode 100644 index 0000000..38dfa0f --- /dev/null +++ b/source/benoit/benoit/render/paint/lch.rs @@ -0,0 +1,108 @@ +/* + 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::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 + + let angle = factor * PI; + + 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); +} + +fn rgb_to_srgb(r: f32, g: f32, b: f32) -> (f32, f32, f32) { + fn srgb(value: f32) -> f32 { + return if value > 7827.0 / 25000000.0 { + 211.0 / 200.0 * value.powf(5.0 / 12.0) - 11.0 / 200.0 + } else { + 298.0 / 25.0 * value + }; + } + + let r = srgb(r); + let g = srgb(g); + let b = srgb(b); + + (r, g, b) +} + +fn xyz_to_rgb(x: f32, y: f32, z: f32) -> (f32, f32, f32) { + let m: [[f32; 0x3]; 0x3] = [ + [ + 12831.0 / 3959.0, -329.0 / 214.0, -1974.0 / 3959.0, + ], + [ + -851781.0 / 878810.0, 1648619.0 / 878810.0, 36519.0 / 878810.0, + ], + [ + 705.0 / 12673.0, -2585.0 / 12673.0, 705.0 / 667.0, + ], + ]; + + let r = m[0x0][0x0] * x + m[0x0][0x1] * y + m[0x0][0x2] * z; + let g = m[0x1][0x0] * x + m[0x1][0x1] * y + m[0x1][0x2] * z; + let b = m[0x2][0x0] * x + m[0x2][0x1] * y + m[0x2][0x2] * z; + + return (r, g, b); +} + +fn lab_to_xyz(l: f32, a: f32, b: f32) -> (f32, f32, f32) { + let kappa: f32 = 24389.0 / 27.0; + let epsilon: f32 = 216.0 / 24389.0; + + let f1 = (l + 16.0) / 116.0; + let f0 = a / 500.0 + f1; + let f2 = f1 - b / 200.0; + + let temporary = (l + 16.0) / 116.0; + + let mut x = f0 * f0 * f0; + let mut y = temporary * temporary * temporary; + let mut z = f2 * f2 * f2; + + if x <= epsilon { x = 1152.0 / 1195.0 * ((116.0 * f0 - 16.0) / kappa) }; + if l <= kappa * epsilon { y = l / kappa }; + if z <= epsilon { z = 986.0 / 1195.0 * ((116.0 * f2 - 16.0) / kappa) }; + + return (x, y, z); +} + +fn lch_to_lab(l: f32, c: f32, h: f32) -> (f32, f32, f32) { + let a = c * h.cos(); + let b = c * h.sin(); + + return (l, a, b); +} diff --git a/source/benoit/benoit/render/paint/sapphire.rs b/source/benoit/benoit/render/paint/sapphire.rs new file mode 100644 index 0000000..4f3e29a --- /dev/null +++ b/source/benoit/benoit/render/paint/sapphire.rs @@ -0,0 +1,40 @@ +/* + 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 fn sapphire(factor: f32) -> (f32, f32, f32) { + let factor = factor % 1.0; + + let factor = (if factor >= 1.0 / 2.0 { + 1.0 - factor + } else { + 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); +} diff --git a/source/benoit/benoit/render/render_data.rs b/source/benoit/benoit/render/render_data.rs new file mode 100644 index 0000000..b0f0851 --- /dev/null +++ b/source/benoit/benoit/render/render_data.rs @@ -0,0 +1,97 @@ +/* + 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::width_height_ratio; + +extern crate rug; + +use rug::Float; +use std::slice::from_raw_parts_mut; + +pub struct RenderData { + canvas_width: u32, + canvas_height: u32, + + centre_real: Float, + centre_imag: Float, + zoom: Float, + + max_iter_count: u32, + + x_offset: f32, + y_offset: f32, + + x_factor: f32, + y_factor: f32, + + iter_count_buffer: *mut u32, + square_dist_buffer: *mut f32, +} + +impl RenderData { + 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); + + return RenderData { + canvas_width: canvas_width, + canvas_height: canvas_height, + + centre_real: centre_real, + centre_imag: centre_imag, + zoom: zoom, + + max_iter_count: max_iter_count, + + x_offset: canvas_width as f32 / -2.0, + y_offset: canvas_height as f32 / -2.0, + + x_factor: 4.0 / canvas_width as f32 * width_ratio, + y_factor: 4.0 / canvas_height as f32 * height_ratio, + + iter_count_buffer: iter_count_buffer.as_mut_ptr(), + square_dist_buffer: square_dist_buffer.as_mut_ptr(), + }; + } + + 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]) { + assert!(row < self.canvas_height); + + let offset = row as isize * self.canvas_width as isize; + + let iter_count = unsafe { from_raw_parts_mut(self.iter_count_buffer.offset(offset), self.canvas_width as usize) }; + let square_dist = unsafe { from_raw_parts_mut(self.square_dist_buffer.offset(offset), self.canvas_width as usize) }; + + return (iter_count, square_dist); + } +} + +unsafe impl Send for RenderData {} +unsafe impl Sync for RenderData {} diff --git a/source/benoit/benoit/task.rs b/source/benoit/benoit/render/render_row.rs index ee53b52..7f9bf9a 100644 --- a/source/benoit/benoit/task.rs +++ b/source/benoit/benoit/render/render_row.rs @@ -21,5 +21,8 @@ If not, see <https://www.gnu.org/licenses/>. */ -pub mod colour_data; -pub mod render_data; +pub mod julia; +pub mod normal; + +pub use julia::*; +pub use normal::*; diff --git a/source/benoit/benoit/render/render_row/julia.rs b/source/benoit/benoit/render/render_row/julia.rs new file mode 100644 index 0000000..7b7c2ef --- /dev/null +++ b/source/benoit/benoit/render/render_row/julia.rs @@ -0,0 +1,84 @@ +/* + 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::render::IteratorFunction; +use crate::benoit::render::render_data::RenderData; + +extern crate rug; + +use rug::{Assign, Float}; +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 (canvas_width, centre_real, centre_imag, _zoom, max_iter_count) = data.input(); + + let (x_offset, y_offset, x_factor, y_factor) = data.consts(); + + for x in 0x0..canvas_width { + // For more information, see render_row::normal. + + let x_temporary = (x as f32 + x_offset) * x_factor; + let y_temporary = (y as f32 + y_offset) * y_factor; + + let ca = centre_real; + let cb = centre_imag; + + // When rendering the Julia fractals, the value of + // (c) remains constant throughout the entire + // canvas. The value of (z) - however - takes the + // position-determined value that (c) would've had. + + let mut za = Float::with_val(PRECISION, x_temporary); + let mut zb = Float::with_val(PRECISION, y_temporary); + + let mut za_prev = Float::with_val(PRECISION, Special::Nan); + let mut zb_prev = Float::with_val(PRECISION, Special::Nan); + + let mut iter_count: u32 = 0x1; + let mut square_dist; + while { + square_dist = Float::with_val(PRECISION, &za * &za + &zb * &zb).to_f32(); + + let periodic = za == za_prev && zb == zb_prev; + if periodic { iter_count = max_iter_count } + + square_dist <= 256.0 && iter_count < max_iter_count + } { + za_prev.assign(&za); + zb_prev.assign(&zb); + + iterator(&mut za, &mut zb, ca, cb); + + iter_count += 0x1; + } + + unsafe { + *iter_count_buffer.get_unchecked_mut( x as usize) = iter_count; + *square_dist_buffer.get_unchecked_mut(x as usize) = square_dist; + } + } +} diff --git a/source/benoit/benoit/render/render_row/normal.rs b/source/benoit/benoit/render/render_row/normal.rs new file mode 100644 index 0000000..b39af7c --- /dev/null +++ b/source/benoit/benoit/render/render_row/normal.rs @@ -0,0 +1,94 @@ +/* + 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::render::IteratorFunction; +use crate::benoit::render::render_data::RenderData; + +extern crate rug; + +use rug::{Assign, Float}; +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 (canvas_width, centre_real, centre_imag, zoom, max_iter_count) = data.input(); + + let (x_offset, y_offset, x_factor, y_factor) = data.consts(); + + for x in 0x0..canvas_width { + let x_temporary = (x as f32 + x_offset) * x_factor; + let y_temporary = (y as f32 + y_offset) * y_factor; + + let ca = { + let mut ca = Float::with_val(PRECISION, x_temporary / zoom); + ca += centre_real; + + ca + }; + + let cb = { + let mut cb = Float::with_val(PRECISION, y_temporary / zoom); + cb -= centre_imag; + + cb + }; + + let mut za = ca.clone(); + let mut zb = cb.clone(); + + let mut za_prev = Float::with_val(PRECISION, Special::Nan); + let mut zb_prev = Float::with_val(PRECISION, Special::Nan); + + let mut iter_count: u32 = 0x1; + let mut square_dist; + while { + square_dist = Float::with_val(PRECISION, &za * &za + &zb * &zb).to_f32(); + // Having a larger escape radius gives better + // results with regard to smoothing. + + // Check if the value is periodic, i.e. its + // sequence repeats. + let periodic = za == za_prev && zb == zb_prev; + if periodic { iter_count = max_iter_count } + + square_dist <= 256.0 && iter_count < max_iter_count + } { + za_prev.assign(&za); + zb_prev.assign(&zb); + + iterator(&mut za, &mut zb, &ca, &cb); + + iter_count += 0x1; + } + + // Sacrifice safety for speed by removing bounds- + // checking. + unsafe { + *iter_count_buffer.get_unchecked_mut( x as usize) = iter_count; + *square_dist_buffer.get_unchecked_mut(x as usize) = square_dist; + } + } +} diff --git a/source/benoit/benoit/task/colour_data/new.rs b/source/benoit/benoit/rendering.rs index 2707018..125798f 100644 --- a/source/benoit/benoit/task/colour_data/new.rs +++ b/source/benoit/benoit/rendering.rs @@ -21,20 +21,30 @@ If not, see <https://www.gnu.org/licenses/>. */ -use crate::benoit::task::colour_data::ColourData; +use crate::benoit::render::RowRenderer; +use crate::benoit::render::render_row; -impl ColourData { - pub fn new(image: &mut [u8], canvas_width: u32, max_iter_count: u32, colour_range: f32, iter_count_buffer: &[u32], square_dist_buffer: &[f32]) -> ColourData { - return ColourData { - canvas_width: canvas_width, +use std::mem::transmute; - max_iter_count: max_iter_count, - colour_range: colour_range, - - iter_count_buffer: iter_count_buffer.as_ptr(), - square_dist_buffer: square_dist_buffer.as_ptr(), +#[derive(Clone, Copy)] +pub enum Rendering { + Julia, + Normal, +} - image: image.as_mut_ptr(), +impl Rendering { + pub fn get_row_renderer(self) -> RowRenderer { + return match self { + Rendering::Julia => render_row::julia, + Rendering::Normal => render_row::normal, }; } + + pub fn cycle(self) -> Self { + let raw = !(self as u8) & 0b00000001; + + let new: Self = unsafe { transmute(raw) }; + + return new; + } } diff --git a/source/benoit/benoit/task/render_data/slice.rs b/source/benoit/benoit/task/render_data/slice.rs deleted file mode 100644 index d2660ee..0000000 --- a/source/benoit/benoit/task/render_data/slice.rs +++ /dev/null @@ -1,37 +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::task::render_data::RenderData; - -use std::slice::from_raw_parts_mut; - -impl RenderData { - pub unsafe fn slice(&self, row: u32) -> (&mut [u32], &mut [f32]) { - let offset = row as isize * self.canvas_width as isize; - - let iter_count = from_raw_parts_mut(self.iter_count_buffer.offset(offset), self.canvas_width as usize); - let dist = from_raw_parts_mut(self.square_dist_buffer.offset(offset), self.canvas_width as usize); - - return (iter_count, dist); - } -} diff --git a/source/benoit/benoit/video.rs b/source/benoit/benoit/video.rs index af060fe..88c853b 100644 --- a/source/benoit/benoit/video.rs +++ b/source/benoit/benoit/video.rs @@ -27,8 +27,11 @@ use sdl2::{Sdl, VideoSubsystem}; use sdl2::render::WindowCanvas; pub mod draw; +pub mod draw_textual_feedback; +pub mod draw_translation_feedback; pub mod initialise; pub mod sync; +pub mod update; pub struct Video { pub sdl: Sdl, diff --git a/source/benoit/benoit/video/draw.rs b/source/benoit/benoit/video/draw.rs index bd5a837..a3d2460 100644 --- a/source/benoit/benoit/video/draw.rs +++ b/source/benoit/benoit/video/draw.rs @@ -21,19 +21,20 @@ If not, see <https://www.gnu.org/licenses/>. */ -use crate::benoit::{FeedbackInfo, PRECISION}; use crate::benoit::video::Video; -extern crate rug; extern crate sdl2; -use rug::Float; use sdl2::pixels::Color; use sdl2::rect::Rect; impl Video { - pub fn draw(&mut self, image: &[u8], canvas_width: u32, scale: u32, feedback_info: Option<&FeedbackInfo>) { - let canvas_size = canvas_width * canvas_width; + 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; for pixel in 0x0..canvas_size { let y = pixel as u32 / canvas_width; @@ -47,77 +48,15 @@ impl Video { Color::RGB(red, green, blue) }; - let rectangle = Rect::new( + let square = Rect::new( (x * scale) as i32, (y * scale) as i32, scale, - scale + scale, ); self.canvas.set_draw_color(colour); - self.canvas.fill_rects(&[rectangle]).unwrap(); - } - - if feedback_info.is_some() { - let feedback_info = unsafe { feedback_info.unwrap_unchecked() }; - - let canvas_width = { - let mut canvas_width = Float::with_val(PRECISION, canvas_width); - canvas_width *= scale; - - canvas_width - }; - - let viewport = { - let ((offset_x, offset_y), width) = { - let zoom_ratio = Float::with_val(PRECISION, feedback_info.next_zoom / feedback_info.prev_zoom); - - let mut width = Float::with_val(PRECISION, 1.0 / &zoom_ratio); - - // Remember that cartesian coordinates have an - // inverted vertical axis compared to those of - // SDL's coordinate system. - - let mut offset_x = feedback_info.next_centre_real.clone(); - let mut offset_y = Float::with_val(PRECISION, -feedback_info.next_centre_imag); - - offset_x -= feedback_info.prev_centre_real; - offset_y += feedback_info.prev_centre_imag; - - offset_x /= 4.0; - offset_y /= 4.0; - - offset_x *= feedback_info.prev_zoom; - offset_y *= feedback_info.prev_zoom; - - let mut zoom_offset = Float::with_val(PRECISION, 1.0 - &width); - zoom_offset /= 2.0; - - offset_x += &zoom_offset; - offset_y += &zoom_offset; - - offset_x *= &canvas_width; - offset_y *= &canvas_width; - width *= &canvas_width; - - ((offset_x.to_f32().round() as i32, offset_y.to_f32().round() as i32), width.to_f32().round() as u32) - }; - - Rect::new( - offset_x, - offset_y, - width, - width, - ) - }; - - self.canvas.set_draw_color(Color::RGBA(0x0, 0x0, 0x0, 0x3F)); - self.canvas.fill_rects(&[viewport]).unwrap(); - - self.canvas.set_draw_color(Color::RGB(0xFF, 0xFF, 0xFF)); - self.canvas.draw_rects(&[viewport]).unwrap(); + self.canvas.fill_rect(square).unwrap(); } - - self.canvas.present(); } } diff --git a/source/benoit/benoit/video/draw_textual_feedback.rs b/source/benoit/benoit/video/draw_textual_feedback.rs new file mode 100644 index 0000000..cfa2307 --- /dev/null +++ b/source/benoit/benoit/video/draw_textual_feedback.rs @@ -0,0 +1,328 @@ +/* + 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::video::Video; + +extern crate rug; +extern crate sdl2; + +use rug::Float; +use sdl2::pixels::Color; +use sdl2::rect::Rect; +use sdl2::render::WindowCanvas; + +impl Video { + pub fn draw_textual_feedback(&mut self, real: &Float, imag: &Float, zoom: &Float, max_iter_count: u32) { + let real_text = format!("REAL: {:.18}", real.to_f64()); + let imag_text = format!("IMAG: {:.18}", imag.to_f64()); + let zoom_text = format!("ZOOM: 2^{:.9}", zoom.to_f64().log2()); + let iter_text = format!("ITER: {}", max_iter_count); + + let string = format!("{real_text}\n{imag_text}\n{zoom_text}\n{iter_text}"); + + let mut offset_x = 0x1; + let mut offset_y = 0x1; + for character in string.chars() { + if character == '\n' { + offset_x = 0x1; + offset_y += TEXTURE_HEIGHT; + continue; + } + + let character = convert_character(character); + + draw_character(&mut self.canvas, offset_x, offset_y, character); + + offset_x += TEXTURE_WIDTH; + } + } +} + +fn draw_character(canvas: &mut WindowCanvas, x: u32, y: u32, character: u8) { + let texture = &FONT[character as usize]; + + for pixel in 0x0..TEXTURE_SIZE { + let alpha = texture[pixel] as u8 * 0xFF; + + let texture_y = pixel as u32 / TEXTURE_WIDTH; + let texture_x = pixel as u32 - texture_y * TEXTURE_WIDTH; + + let square = Rect::new( + (x * CHARACTER_SCALE + texture_x * CHARACTER_SCALE) as i32, + (y * CHARACTER_SCALE + texture_y * CHARACTER_SCALE) as i32, + CHARACTER_SCALE, + CHARACTER_SCALE, + ); + + canvas.set_draw_color(Color::RGBA(0xFF, 0xFF, 0xFF, alpha)); + canvas.fill_rect(square).unwrap(); + } +} + +fn convert_character(input: char) -> u8 { + return match input { + ' ' => 0x00, + 'A' => 0x01, + 'E' => 0x02, + 'G' => 0x03, + 'I' => 0x04, + 'L' => 0x05, + 'M' => 0x06, + 'O' => 0x07, + 'R' => 0x08, + 'T' => 0x09, + 'Z' => 0x0A, + '0' => 0x0B, + '1' => 0x0C, + '2' => 0x0D, + '3' => 0x0E, + '4' => 0x0F, + '5' => 0x10, + '6' => 0x11, + '7' => 0x12, + '8' => 0x13, + '9' => 0x14, + '-' => 0x15, + '^' => 0x16, + '.' => 0x17, + ':' => 0x18, + _ => 0x19, + }; +} + +const CHARACTER_SCALE: u32 = 0x2; + +const TEXTURE_WIDTH: u32 = 0x6; +const TEXTURE_HEIGHT: u32 = 0x6; +const TEXTURE_SIZE: usize = TEXTURE_HEIGHT as usize * TEXTURE_WIDTH as usize; + +const FONT: [[bool; TEXTURE_SIZE]; 0x1A] = [ + [ + false, false, false, false, false, false, + false, false, false, false, false, false, + false, false, false, false, false, false, + false, false, false, false, false, false, + false, false, false, false, false, false, + false, false, false, false, false, false, + ], + [ + false, true, true, true, false, false, + true, true, true, true, true, false, + true, true, false, true, true, false, + true, true, true, true, true, false, + true, true, false, true, true, false, + false, false, false, false, false, false, + ], + [ + true, true, true, true, true, false, + true, true, false, false, false, false, + true, true, true, true, false, false, + true, true, false, false, false, false, + true, true, true, true, true, false, + false, false, false, false, false, false, + ], + [ + true, true, true, true, true, false, + true, true, false, false, false, false, + true, true, false, true, true, false, + true, true, false, false, true, false, + true, true, true, true, true, false, + false, false, false, false, false, false, + ], + [ + true, true, true, true, true, false, + false, false, true, false, false, false, + false, false, true, false, false, false, + false, false, true, false, false, false, + true, true, true, true, true, false, + false, false, false, false, false, false, + ], + [ + true, true, false, false, false, false, + true, true, false, false, false, false, + true, true, false, false, false, false, + true, true, false, false, false, false, + true, true, true, true, true, false, + false, false, false, false, false, false, + ], + [ + true, true, false, true, true, false, + true, true, true, true, true, false, + true, true, true, true, true, false, + true, true, false, true, true, false, + true, true, false, true, true, false, + false, false, false, false, false, false, + ], + [ + false, true, true, true, false, false, + true, true, false, true, true, false, + true, true, false, true, true, false, + true, true, false, true, true, false, + false, true, true, true, false, false, + false, false, false, false, false, false, + ], + [ + true, true, true, true, false, false, + true, true, false, true, true, false, + true, true, true, true, false, false, + true, true, false, true, true, false, + true, true, false, false, true, false, + false, false, false, false, false, false, + ], + [ + true, true, true, true, true, false, + false, false, true, false, false, false, + false, false, true, false, false, false, + false, false, true, false, false, false, + false, false, true, false, false, false, + false, false, false, false, false, false, + ], + [ + true, true, true, true, true, false, + false, false, true, true, true, false, + false, true, true, true, false, false, + true, true, true, false, false, false, + true, true, true, true, true, false, + false, false, false, false, false, false, + ], + [ + false, true, true, true, false, false, + true, true, false, true, true, false, + true, true, false, true, true, false, + true, true, false, true, true, false, + false, true, true, true, false, false, + false, false, false, false, false, false, + ], + [ + false, true, true, false, false, false, + true, true, true, false, false, false, + false, false, true, false, false, false, + false, false, true, false, false, false, + true, true, true, true, true, false, + false, false, false, false, false, false, + ], + [ + false, true, true, true, false, false, + true, false, false, true, true, false, + false, false, true, true, false, false, + false, true, true, false, false, false, + true, true, true, true, true, false, + false, false, false, false, false, false, + ], + [ + true, true, true, true, true, false, + true, false, false, true, true, false, + false, false, true, true, false, false, + false, false, false, true, true, false, + true, true, true, true, false, false, + false, false, false, false, false, false, + ], + [ + false, true, false, true, true, false, + true, true, false, true, true, false, + true, true, true, true, true, false, + false, false, false, true, true, false, + false, false, false, true, true, false, + false, false, false, false, false, false, + ], + [ + true, true, true, true, true, false, + true, true, false, false, false, false, + true, true, true, true, true, false, + false, false, false, true, true, false, + true, true, true, true, false, false, + false, false, false, false, false, false, + ], + [ + false, true, true, true, true, false, + true, true, false, false, false, false, + true, true, true, true, true, false, + true, true, false, true, true, false, + false, true, true, true, true, false, + false, false, false, false, false, false, + ], + [ + true, true, true, true, true, false, + false, false, false, true, true, false, + false, false, true, true, false, false, + false, true, true, false, false, false, + true, true, false, false, false, false, + false, false, false, false, false, false, + ], + [ + false, true, true, true, false, false, + true, true, false, true, true, false, + false, true, true, true, false, false, + true, true, false, true, true, false, + false, true, true, true, false, false, + false, false, false, false, false, false, + ], + [ + false, true, true, true, true, false, + true, true, false, true, true, false, + true, true, true, true, true, false, + false, false, false, true, true, false, + true, true, true, true, false, false, + false, false, false, false, false, false, + ], + [ + false, false, false, false, false, false, + false, false, false, false, false, false, + true, true, true, true, true, false, + false, false, false, false, false, false, + false, false, false, false, false, false, + false, false, false, false, false, false, + ], + [ + false, false, true, false, false, false, + false, true, true, true, false, false, + true, true, false, true, true, false, + false, false, false, false, false, false, + false, false, false, false, false, false, + false, false, false, false, false, false, + ], + [ + false, false, false, false, false, false, + false, false, false, false, false, false, + false, false, false, false, false, false, + true, false, false, false, false, false, + true, false, false, false, false, false, + false, false, false, false, false, false, + ], + [ + true, false, false, false, false, false, + false, false, false, false, false, false, + false, false, false, false, false, false, + false, false, false, false, false, false, + true, false, false, false, false, false, + false, false, false, false, false, false, + ], + [ + true, true, false, false, false, false, + false, true, false, false, false, false, + true, true, false, false, false, false, + false, false, false, false, false, false, + true, false, false, false, false, false, + false, false, false, false, false, false, + ], +]; diff --git a/source/benoit/benoit/video/draw_translation_feedback.rs b/source/benoit/benoit/video/draw_translation_feedback.rs new file mode 100644 index 0000000..aeb7463 --- /dev/null +++ b/source/benoit/benoit/video/draw_translation_feedback.rs @@ -0,0 +1,95 @@ +/* + 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::{FeedbackInfo, PRECISION, width_height_ratio}; +use crate::benoit::video::Video; + +extern crate rug; +extern crate sdl2; + +use rug::Float; +use sdl2::pixels::Color; +use sdl2::rect::Rect; + +impl Video { + pub fn draw_translation_feedback(&mut self, canvas_width: u32, canvas_height: u32, scale: u32, feedback_info: &FeedbackInfo) { + let (width_ratio, height_ratio) = width_height_ratio(canvas_width, canvas_height); + + let canvas_width = Float::with_val(PRECISION, canvas_width * scale); + let canvas_height = Float::with_val(PRECISION, canvas_height * scale); + + let viewport = { + let (offset_x, offset_y, width, height) = { + let zoom_ratio = Float::with_val(PRECISION, feedback_info.next_zoom / feedback_info.prev_zoom); + + let mut width = Float::with_val(PRECISION, 1.0 / &zoom_ratio); + let mut height = Float::with_val(PRECISION, 1.0 / &zoom_ratio); + + // Remember that cartesian coordinates have an + // inverted vertical axis compared to those of + // SDL's coordinate system. + + let mut offset_x = feedback_info.next_centre_real.clone(); + let mut offset_y = Float::with_val(PRECISION, -feedback_info.next_centre_imag); + + offset_x -= feedback_info.prev_centre_real; + offset_y += feedback_info.prev_centre_imag; + + offset_x /= 4.0; + offset_y /= 4.0; + + offset_x *= feedback_info.prev_zoom; + offset_y *= feedback_info.prev_zoom; + + offset_x /= width_ratio; + offset_y /= height_ratio; + + let mut zoom_offset = Float::with_val(PRECISION, 1.0 - &width); + zoom_offset /= 2.0; + + offset_x += &zoom_offset; + offset_y += &zoom_offset; + + offset_x *= &canvas_width; + offset_y *= &canvas_height; + width *= &canvas_width; + height *= &canvas_height; + + (offset_x.to_f32().round() as i32, offset_y.to_f32().round() as i32, width.to_f32().round() as u32, height.to_f32().round() as u32) + }; + + Rect::new( + offset_x, + offset_y, + width, + height, + ) + }; + + self.canvas.set_draw_color(Color::RGBA(0x0, 0x0, 0x0, 0x7F)); + self.canvas.fill_rect(viewport).unwrap(); + + self.canvas.set_draw_color(Color::RGB(0xFF, 0xFF, 0xFF)); + self.canvas.draw_rect(viewport).unwrap(); + } +} diff --git a/source/benoit/benoit/video/initialise.rs b/source/benoit/benoit/video/initialise.rs index ae633b3..dcd92f7 100644 --- a/source/benoit/benoit/video/initialise.rs +++ b/source/benoit/benoit/video/initialise.rs @@ -21,19 +21,22 @@ If not, see <https://www.gnu.org/licenses/>. */ +use crate::benoit::VERSION; use crate::benoit::video::Video; extern crate sdl2; -use crate::benoit::VERSION; +use sdl2::pixels::Color; use sdl2::render::BlendMode; impl Video { - pub fn initialise(canvas_width: u32, scale: u32) -> Video { + 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"); - let mut window_builder = sdl_video.window(format!("Beno\u{00EE}t {:X}.{:X}.{:X}", VERSION.major, VERSION.minor, VERSION.patch).as_str(), canvas_width * scale, canvas_width * scale); + let window_title = format!("BENO\u{CE}T {:X}.{:X}.{:X}", VERSION.major, VERSION.minor, VERSION.patch); + + let mut window_builder = sdl_video.window(window_title.as_str(), canvas_width * scale, canvas_height * scale); window_builder.borderless(); window_builder.position_centered(); @@ -46,6 +49,11 @@ impl Video { // We only want to scale the render, not the // feedback, so we can't use SDL's scaling feature. + let clear_colour = Color::RGB(0x00, 0x00, 0x00); + canvas.set_draw_color(clear_colour); + canvas.clear(); + canvas.present(); + return Video { sdl: sdl, sdl_video: sdl_video, diff --git a/source/benoit/benoit/app/get_row_renderer.rs b/source/benoit/benoit/video/update.rs index 050d14f..eab330a 100644 --- a/source/benoit/benoit/app/get_row_renderer.rs +++ b/source/benoit/benoit/video/update.rs @@ -21,14 +21,10 @@ If not, see <https://www.gnu.org/licenses/>. */ -use crate::benoit::app::App; -use crate::benoit::app::RowRenderer; +use crate::benoit::video::Video; -impl App { - pub fn get_row_renderer(julia: bool) -> RowRenderer { - return match julia { - false => App::render_row_normal, - true => App::render_row_julia, - }; +impl Video { + pub fn update(&mut self) { + self.canvas.present(); } } diff --git a/source/benoit/benoit/width_height_ratio.rs b/source/benoit/benoit/width_height_ratio.rs new file mode 100644 index 0000000..c4afe91 --- /dev/null +++ b/source/benoit/benoit/width_height_ratio.rs @@ -0,0 +1,30 @@ +/* + 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 fn width_height_ratio(width: u32, height: u32) -> (f32, f32) { + return if width > height { + (1.0, height as f32 / width as f32) + } else { + (width as f32 / height as f32, 1.0) + }; +} |