diff options
34 files changed, 552 insertions, 344 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index daeaf95..0a04efc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,13 @@ +# 2.7.0 + +* Bump minor version +* Rework animations (support multiple variables) +* Rework terminal arguments +* Improve error handling (avoid panics and assertions) +* Don't validate configuration immediately +* Update messages +* Rename 'burningship' to 'burning_ship' in configuration + # 2.6.3 * Add help @@ -1,6 +1,6 @@ [package] name = "benoit" -version = "2.6.3" +version = "2.7.0" authors = ["Gabriel Bjørnager Jensen"] edition = "2021" description = "Multithreaded Mandelbrot renderer with support for PNG and WebP encoding." diff --git a/source/benoit/benoit.rs b/source/benoit/benoit.rs index b7878b2..549e25a 100644 --- a/source/benoit/benoit.rs +++ b/source/benoit/benoit.rs @@ -36,8 +36,8 @@ pub mod video; pub const VERSION: (u32, u32, u32) = ( 0x2, // Major - 0x6, // Minor - 0x3, // Patch + 0x7, // Minor + 0x0, // Patch ); pub const PRECISION: u32 = 0x80; diff --git a/source/benoit/benoit/app.rs b/source/benoit/benoit/app.rs index 61446be..3f487f7 100644 --- a/source/benoit/benoit/app.rs +++ b/source/benoit/benoit/app.rs @@ -29,10 +29,10 @@ extern crate rug; use rug::Float; -pub mod configure; pub mod draw_feedback; pub mod drop; pub mod handle_keys; +pub mod new; pub mod poll_events; pub mod print_controls; pub mod render; diff --git a/source/benoit/benoit/app/handle_keys.rs b/source/benoit/benoit/app/handle_keys.rs index 04e5a54..df35ea5 100644 --- a/source/benoit/benoit/app/handle_keys.rs +++ b/source/benoit/benoit/app/handle_keys.rs @@ -31,8 +31,6 @@ extern crate sdl2; use rug::{Assign, Float}; use sdl2::keyboard::{KeyboardState, Scancode}; -pub const MIN_COLOUR_RANGE: f32 = 2.0; - impl App { #[must_use] pub(super) fn handle_keys(&mut self, scan_code: Scancode, state: KeyboardState) -> bool { @@ -66,7 +64,7 @@ impl App { self.colour_range = match scan_code { Scancode::Up => self.colour_range * COLOUR_RANGE_FACTOR, - Scancode::Down => (self.colour_range / COLOUR_RANGE_FACTOR).max(MIN_COLOUR_RANGE), + Scancode::Down => (self.colour_range / COLOUR_RANGE_FACTOR).max(Configuration::MIN_COLOUR_RANGE), _ => self.colour_range, }; @@ -161,8 +159,12 @@ impl App { fn dump_info(&self) { eprintln!("info dump: the {}", self.fractal.name()); - eprintln!(" c = {}{:+}i ({}x)", &self.centre.real, &self.centre.imag, &self.zoom); - eprintln!(" w = {}{:+}i", &self.extra.real, &self.extra.imag); - eprintln!(" max. iter.: {}, col. range: {}", self.max_iter_count, self.colour_range); + eprintln!(" re(c): {}", self.centre.real); + eprintln!(" im(c): {}", self.centre.imag); + eprintln!(" re(w): {}", self.extra.real); + eprintln!(" im(w): {}", self.extra.imag); + eprintln!(" zoom: {}", self.zoom); + eprintln!(" max. iter count: {}", self.max_iter_count); + eprintln!(" col. range: {}", self.colour_range); } } diff --git a/source/benoit/benoit/app/configure.rs b/source/benoit/benoit/app/new.rs index 1af3d60..f19380a 100644 --- a/source/benoit/benoit/app/configure.rs +++ b/source/benoit/benoit/app/new.rs @@ -21,29 +21,33 @@ If not, see <https://www.gnu.org/licenses/>. */ +use crate::benoit::PRECISION; use crate::benoit::app::App; use crate::benoit::complex::Complex; use crate::benoit::configuration::Configuration; +extern crate rug; + +use rug::Float; + impl App { #[must_use] - pub fn configure(configuration: Configuration) -> App { + pub fn new(canvas_width: u32, canvas_height: u32) -> App { return App { - fractal: configuration.fractal, - - canvas_width: configuration.canvas_width, - canvas_height: configuration.canvas_height, - scale: configuration.scale, + fractal: Configuration::DEFAULT_FRACTAL, - centre: Complex::new(configuration.centre_real, configuration.centre_imag), - zoom: configuration.zoom, + canvas_width: canvas_width, + canvas_height: canvas_height, + scale: 0x2, - extra: Complex::new(configuration.extra_real, configuration.extra_imag), + centre: Complex::new(Float::with_val(PRECISION, Configuration::DEFAULT_CENTRE.0), Float::with_val(PRECISION, Configuration::DEFAULT_CENTRE.1)), + extra: Complex::new(Float::with_val(PRECISION, Configuration::DEFAULT_EXTRA.0), Float::with_val(PRECISION, Configuration::DEFAULT_EXTRA.1)), + zoom: Float::with_val(PRECISION, Configuration::DEFAULT_ZOOM), - max_iter_count: configuration.max_iter_count, + max_iter_count: Configuration::DEFAULT_MAX_ITER_COUNT, - palette: configuration.palette, - colour_range: configuration.colour_range, + palette: Configuration::DEFAULT_PALETTE, + colour_range: Configuration::DEFAULT_COLOUR_RANGE, do_render: true, do_textual_feedback: false, diff --git a/source/benoit/benoit/app/print_controls.rs b/source/benoit/benoit/app/print_controls.rs index f6f2612..99cab61 100644 --- a/source/benoit/benoit/app/print_controls.rs +++ b/source/benoit/benoit/app/print_controls.rs @@ -25,40 +25,41 @@ use crate::benoit::app::App; impl App { pub(super) fn print_controls() { - println!("Controls:"); - println!("- \u{1B}[1mW\u{1B}[0m Translate +Im"); - println!("- \u{1B}[1mA\u{1B}[0m Translate -Re"); - println!("- \u{1B}[1mS\u{1B}[0m Translate -Im"); - println!("- \u{1B}[1mD\u{1B}[0m Translate +Re"); + println!(""); + println!("controls:"); + println!("- \u{1B}[1mW\u{1B}[0m translate +Im"); + println!("- \u{1B}[1mA\u{1B}[0m translate -Re"); + println!("- \u{1B}[1mS\u{1B}[0m translate -Im"); + println!("- \u{1B}[1mD\u{1B}[0m translate +Re"); println!(); - println!("- \u{1B}[1mQ\u{1B}[0m Zoom out"); - println!("- \u{1B}[1mE\u{1B}[0m Zoom in"); + println!("- \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!("- \u{1B}[1mR\u{1B}[0m decrease max. iteration count"); + println!("- \u{1B}[1mF\u{1B}[0m increase max. iteration count"); println!(); - println!("- \u{1B}[1mLEFT ALT\u{1B}[0m Cycle to previous fractal"); - println!("- \u{1B}[1mRIGHT ALT\u{1B}[0m Cycle to next fractal"); - println!("- \u{1B}[1mTAB\u{1B}[0m Toggle Julia"); - println!("- \u{1B}[1mLEFT CTRL\u{1B}[0m Toggle inverse"); + println!("- \u{1B}[1mLEFT ALT\u{1B}[0m cycle to previous fractal"); + println!("- \u{1B}[1mRIGHT ALT\u{1B}[0m cycle to next fractal"); + println!("- \u{1B}[1mTAB\u{1B}[0m toggle Julia"); + println!("- \u{1B}[1mLEFT CTRL\u{1B}[0m toggle inverse"); println!(); - println!("- \u{1B}[1mLEFT\u{1B}[0m Cycle to previous palette"); - println!("- \u{1B}[1mRIGHT\u{1B}[0m Cycle to next palette"); - println!("- \u{1B}[1mUP\u{1B}[0m Increase colour range"); - println!("- \u{1B}[1mDOWN\u{1B}[0m Decrease colour range"); + println!("- \u{1B}[1mLEFT\u{1B}[0m cycle to previous palette"); + println!("- \u{1B}[1mRIGHT\u{1B}[0m cycle to next palette"); + println!("- \u{1B}[1mUP\u{1B}[0m increase colour range"); + println!("- \u{1B}[1mDOWN\u{1B}[0m decrease colour range"); println!(); - println!("- \u{1B}[1mF1\u{1B}[0m Toggle textual feedback"); - println!("- \u{1B}[1mZ\u{1B}[0m Print centre value (c)"); + println!("- \u{1B}[1mF1\u{1B}[0m toggle textual feedback"); + println!("- \u{1B}[1mZ\u{1B}[0m print centre value (c)"); println!(); - println!("- \u{1B}[1mC\u{1B}[0m Render frame"); + println!("- \u{1B}[1mC\u{1B}[0m render frame"); println!(); println!("Controls (holding \u{1B}[1mSHIFT\u{1B}[0m):"); - println!("- \u{1B}[1mW\u{1B}[0m Perturbate/translate +Im"); - println!("- \u{1B}[1mA\u{1B}[0m Perturbate/translate -Re"); - println!("- \u{1B}[1mS\u{1B}[0m Perturbate/translate -Im"); - println!("- \u{1B}[1mD\u{1B}[0m Perturbate/translate +Re"); + println!("- \u{1B}[1mW\u{1B}[0m perturbate/translate +Im"); + println!("- \u{1B}[1mA\u{1B}[0m perturbate/translate -Re"); + println!("- \u{1B}[1mS\u{1B}[0m perturbate/translate -Im"); + println!("- \u{1B}[1mD\u{1B}[0m perturbate/translate +Re"); println!(); - println!("- \u{1B}[1mC\u{1B}[0m Render frame"); + println!("- \u{1B}[1mC\u{1B}[0m render frame"); println!(); } }
\ No newline at end of file diff --git a/source/benoit/benoit/app/render.rs b/source/benoit/benoit/app/render.rs index 7f4b132..1ba6c01 100644 --- a/source/benoit/benoit/app/render.rs +++ b/source/benoit/benoit/app/render.rs @@ -26,7 +26,7 @@ use crate::benoit::render::Render; use std::time::Instant; impl App { - pub(super) fn render(&self, render: &mut Render) { + pub(super) fn render(&self, render: &mut Render) -> Result<(), String> { eprint!("rendering..."); let time_start = Instant::now(); @@ -37,10 +37,12 @@ impl App { &self.zoom, &self.extra, self.max_iter_count, - ); + )?; let render_time = time_start.elapsed(); - eprintln!(" {:.3}ms", render_time.as_micros() as f32 / 1000.0); + eprintln!("\rrendered: {:.3}ms", render_time.as_micros() as f32 / 1000.0); + + return Ok(()); } }
\ No newline at end of file diff --git a/source/benoit/benoit/app/run.rs b/source/benoit/benoit/app/run.rs index 1810faa..9652b8e 100644 --- a/source/benoit/benoit/app/run.rs +++ b/source/benoit/benoit/app/run.rs @@ -33,27 +33,30 @@ use std::time::Instant; impl App { #[must_use] - pub fn run(mut self) -> i32 { - let mut video = Video::initialise(self.canvas_width, self.canvas_height, self.scale); + pub fn run(mut self) -> Result<(), String> { + let mut video = Video::initialise(self.canvas_width, self.canvas_height, self.scale)?; App::print_controls(); - let mut event_pump = video.sdl.event_pump().expect("unable to get event pump"); + let mut event_pump = match video.sdl.event_pump() { + Ok( pump) => pump, + Err(_) => return Err("unable to get event pump".to_string()), + }; - let mut render = Render::allocate(self.canvas_width, self.canvas_height); - let mut image = Image::allocate( self.canvas_width, self.canvas_height); + let mut render = Render::allocate(self.canvas_width, self.canvas_height)?; + let mut image = Image::allocate( self.canvas_width, self.canvas_height)?; // Used for translation feedback: let mut prev_centre = self.centre.clone(); let mut prev_zoom = self.zoom.clone(); loop { - let frame_start = Instant::now(); + let start_frame = Instant::now(); - if self.poll_events(&mut event_pump) { break } + if self.poll_events(&mut event_pump) { break }; if self.do_render { - self.render(&mut render); + self.render(&mut render)?; prev_centre.assign(&self.centre); prev_zoom.assign( &self.zoom); @@ -61,15 +64,15 @@ impl App { self.do_render = false; }; - image.colour(&render, self.palette, self.max_iter_count, self.colour_range); + image.colour(&render, self.palette, self.max_iter_count, self.colour_range)?; video.draw_image(&image, self.scale); self.draw_feedback(&mut video, &prev_centre, &prev_zoom); video.update(); - video.sync(&frame_start); + video.sync(&start_frame)?; } - return 0x0; + return Ok(()); } }
\ No newline at end of file diff --git a/source/benoit/benoit/configuration.rs b/source/benoit/benoit/configuration.rs index ca96a54..511a9aa 100644 --- a/source/benoit/benoit/configuration.rs +++ b/source/benoit/benoit/configuration.rs @@ -39,24 +39,28 @@ pub struct Configuration { pub canvas_width: u32, pub canvas_height: u32, - pub scale: u32, - pub frame_start: u32, - pub frame_stop: u32, - - pub centre_real: Float, - pub centre_imag: Float, - pub zoom: Float, - - pub extra_real: Float, - pub extra_imag: Float, - - pub max_iter_count: u32, - - pub palette: Palette, - pub colour_range: f32, + pub palette: Palette, pub dump_path: String, pub image_format: ImageFormat, + + pub start_frame: u32, + pub start_centre_real: Float, + pub start_centre_imag: Float, + pub start_extra_real: Float, + pub start_extra_imag: Float, + pub start_zoom: Float, + pub start_max_iter_count: u32, + pub start_colour_range: f32, + + pub stop_frame: u32, + pub stop_centre_real: Float, + pub stop_centre_imag: Float, + pub stop_extra_real: Float, + pub stop_extra_imag: Float, + pub stop_zoom: Float, + pub stop_max_iter_count: u32, + pub stop_colour_range: f32, } impl Configuration { @@ -70,4 +74,8 @@ impl Configuration { pub const DEFAULT_PALETTE: Palette = Palette::Fire; pub const DEFAULT_COLOUR_RANGE: f32 = 64.0; + + pub const MIN_CANVAS_WIDTH: u32 = 0x2; + pub const MIN_MAX_ITER_COUNT: u32 = 0x1; + pub const MIN_COLOUR_RANGE: f32 = 2.0; } diff --git a/source/benoit/benoit/configuration/default.rs b/source/benoit/benoit/configuration/default.rs index 24d28b1..ecb6f58 100644 --- a/source/benoit/benoit/configuration/default.rs +++ b/source/benoit/benoit/configuration/default.rs @@ -38,25 +38,29 @@ impl Configuration { fractal: Self::DEFAULT_FRACTAL, canvas_width: 0x100, - canvas_height: 0xC0, - scale: 0x2, - frame_start: 0x0, - frame_stop: 0x0, - - centre_real: Float::with_val(PRECISION, Self::DEFAULT_CENTRE.0), - centre_imag: Float::with_val(PRECISION, Self::DEFAULT_CENTRE.1), - zoom: Float::with_val(PRECISION, Self::DEFAULT_ZOOM), - - extra_real: Float::with_val(PRECISION, Self::DEFAULT_EXTRA.0), - extra_imag: Float::with_val(PRECISION, Self::DEFAULT_EXTRA.1), - - max_iter_count: Self::DEFAULT_MAX_ITER_COUNT, - - palette: Self::DEFAULT_PALETTE, - colour_range: Self::DEFAULT_COLOUR_RANGE, + canvas_height: 0x100, + palette: Self::DEFAULT_PALETTE, dump_path: "./render".to_string(), image_format: ImageFormat::Png, + + start_frame: 0x0, + start_centre_real: Float::with_val(PRECISION, Self::DEFAULT_CENTRE.0), + start_centre_imag: Float::with_val(PRECISION, Self::DEFAULT_CENTRE.1), + start_extra_real: Float::with_val(PRECISION, Self::DEFAULT_EXTRA.0), + start_extra_imag: Float::with_val(PRECISION, Self::DEFAULT_EXTRA.1), + start_zoom: Float::with_val(PRECISION, Self::DEFAULT_ZOOM), + start_max_iter_count: Self::DEFAULT_MAX_ITER_COUNT, + start_colour_range: Self::DEFAULT_COLOUR_RANGE, + + stop_frame: 0xF, + stop_centre_real: Float::with_val(PRECISION, Self::DEFAULT_CENTRE.0), + stop_centre_imag: Float::with_val(PRECISION, Self::DEFAULT_CENTRE.1), + stop_extra_real: Float::with_val(PRECISION, Self::DEFAULT_EXTRA.0), + stop_extra_imag: Float::with_val(PRECISION, Self::DEFAULT_EXTRA.1), + stop_zoom: Float::with_val(PRECISION, Self::DEFAULT_ZOOM), + stop_max_iter_count: Self::DEFAULT_MAX_ITER_COUNT, + stop_colour_range: Self::DEFAULT_COLOUR_RANGE, }; } } diff --git a/source/benoit/benoit/configuration/load.rs b/source/benoit/benoit/configuration/load.rs index 8c02be9..4995163 100644 --- a/source/benoit/benoit/configuration/load.rs +++ b/source/benoit/benoit/configuration/load.rs @@ -43,129 +43,128 @@ impl Configuration { let mut configuration = Configuration::default(); let configuration_text = match read(path) { - Ok(content) => String::from_utf8_lossy(&content).to_string(), - Err(..) => panic!("unable to read configuration file"), + Ok( content) => String::from_utf8_lossy(&content).to_string(), + Err(_) => return Err("unable to read configuration file".to_string()), }; - let configuration_table = Table::from_str(configuration_text.as_str()).expect("unable to parse configuration"); + let base_table = match Table::from_str(configuration_text.as_str()) { + Ok( table) => table, + Err(_) => return Err("unable to parse configuration".to_string()), + }; - get_integer(&mut configuration.thread_count, &configuration_table, "thread_count"); + let start_table = get_table(&base_table, "start")?; + let stop_table = get_table(&base_table, "stop")?; - get_boolean(&mut configuration.fractal.inverse, &configuration_table, "inverse"); - get_boolean(&mut configuration.fractal.julia, &configuration_table, "julia"); + get_integer(&mut configuration.thread_count, &base_table, "thread_count")?; - get_integer(&mut configuration.canvas_width, &configuration_table, "canvas_width"); - get_integer(&mut configuration.canvas_height, &configuration_table, "canvas_height"); - get_integer(&mut configuration.frame_start, &configuration_table, "frame_start"); - get_integer(&mut configuration.frame_stop, &configuration_table, "frame_stop"); + get_boolean(&mut configuration.fractal.inverse, &base_table, "inverse")?; + get_boolean(&mut configuration.fractal.julia, &base_table, "julia")?; - get_bigfloat(&mut configuration.centre_real, &configuration_table, "real"); - get_bigfloat(&mut configuration.centre_imag, &configuration_table, "imaginary"); - get_bigfloat(&mut configuration.extra_real, &configuration_table, "extra_real"); - get_bigfloat(&mut configuration.extra_imag, &configuration_table, "extra_imaginary"); - get_bigfloat(&mut configuration.zoom, &configuration_table, "zoom"); + get_integer(&mut configuration.canvas_width, &base_table, "canvas_width")?; + get_integer(&mut configuration.canvas_height, &base_table, "canvas_height")?; - get_integer(&mut configuration.max_iter_count, &configuration_table, "maximum_iteration_count"); + if let Some(start_table) = start_table { + get_integer( &mut configuration.start_frame, &start_table, "frame")?; + get_bigfloat(&mut configuration.start_centre_real, &start_table, "real")?; + get_bigfloat(&mut configuration.start_centre_imag, &start_table, "imaginary")?; + get_bigfloat(&mut configuration.start_extra_real, &start_table, "extra_real")?; + get_bigfloat(&mut configuration.start_extra_imag, &start_table, "extra_imaginary")?; + get_bigfloat(&mut configuration.start_zoom, &start_table, "zoom")?; + get_integer( &mut configuration.start_max_iter_count, &start_table, "maximum_iteration_count")?; + get_float( &mut configuration.start_colour_range, &start_table, "colour_range")?; + } - get_float(&mut configuration.colour_range, &configuration_table, "colour_range"); + if let Some(stop_table) = stop_table { + get_integer( &mut configuration.stop_frame, &stop_table, "frame")?; + get_bigfloat(&mut configuration.stop_centre_real, &stop_table, "real")?; + get_bigfloat(&mut configuration.stop_centre_imag, &stop_table, "imaginary")?; + get_bigfloat(&mut configuration.stop_extra_real, &stop_table, "extra_real")?; + get_bigfloat(&mut configuration.stop_extra_imag, &stop_table, "extra_imaginary")?; + get_bigfloat(&mut configuration.stop_zoom, &stop_table, "zoom")?; + get_integer( &mut configuration.stop_max_iter_count, &stop_table, "maximum_iteration_count")?; + get_float( &mut configuration.stop_colour_range, &stop_table, "colour_range")?; + } - // Enumerations: - if let Some(name) = get_string(&configuration_table, "fractal") { + // Strings: + if let Some(name) = get_string(&base_table, "fractal")? { configuration.fractal.kind = FractalKind::from_str(name.as_str())?; } - if let Some(name) = get_string(&configuration_table, "palette") { + if let Some(name) = get_string(&base_table, "palette")? { configuration.palette = Palette::from_str(name.as_str())?; } - if let Some(path) = get_string(&configuration_table, "dump_path") { + if let Some(path) = get_string(&base_table, "dump_path")? { configuration.dump_path = path.clone(); } - if let Some(name) = get_string(&configuration_table, "image_format") { + if let Some(name) = get_string(&base_table, "image_format")? { configuration.image_format = ImageFormat::from_str(name.as_str())?; } - validate_configuration(&configuration)?; - return Ok(configuration); } } -fn validate_configuration(configuration: &Configuration) -> Result<(), &str> { - // We allow thread counts of zero as those signal - // automatic thread count detection. - - if configuration.canvas_width == 0x0 || configuration.canvas_height == 0x0 { - return Err("only non-zero values for canvas_width and canvas_height are allowed"); - } - - if configuration.scale == 0x0 { - return Err("only non-zero values for scale are allowed"); - } - - if configuration.frame_start > configuration.frame_stop { - return Err("frame_start may not be greater than frame_stop"); - } - - if configuration.max_iter_count == 0x0 { - return Err("only non-zero values for maximum_iteration_count are allowed"); - } - - if configuration.colour_range < 2.0 { - return Err("only values greater than two are allowed for colour_range"); - } - - return Ok(()); -} - fn get_value<'a>(table: &'a Table, name: &str) -> Option<&'a Value> { if !table.contains_key(name) { return None }; return Some(&table[name]); } -fn get_boolean(buffer: &mut bool, table: &Table, name: &str) { +fn get_table<'a>(table: &'a Table, name: &str) -> Result<Option<&'a Table>, String> { + return match get_value(table, name) { + Some(Value::Table(table)) => Ok(Some(table)), + Some(_) => Err(format!("\"{name}\" should be a section")), + _ => Ok(None), + }; +} + +fn get_boolean(buffer: &mut bool, table: &Table, name: &str) -> Result<(), String> { match get_value(table, name) { Some(Value::Boolean(value)) => *buffer = *value, - Some(_) => panic!("\"{name}\" should be a boolean"), + Some(_) => return Err(format!("\"{name}\" should be a boolean")), _ => {}, }; + return Ok(()) } -fn get_integer(buffer: &mut u32, table: &Table, name: &str) { +fn get_integer(buffer: &mut u32, table: &Table, name: &str) -> Result<(), String> { match get_value(table, name) { Some(Value::Integer(value)) => *buffer = (*value) as u32, - Some(_) => panic!("\"{name}\" should be an integer"), + Some(_) => return Err(format!("\"{name}\" should be an integer")), _ => {}, }; + return Ok(()) } -fn get_float(buffer: &mut f32, table: &Table, name: &str) { +fn get_float(buffer: &mut f32, table: &Table, name: &str) -> Result<(), String> { match get_value(table, name) { Some(Value::Float(value)) => *buffer = (*value) as f32, - Some(_) => panic!("\"{name}\" should be a float"), + Some(_) => return Err(format!("\"{name}\" should be a float")), _ => {}, }; + return Ok(()) } -fn get_bigfloat(buffer: &mut Float, table: &Table, name: &str) { - return match get_value(table, name) { +fn get_bigfloat(buffer: &mut Float, table: &Table, name: &str) -> Result<(), String> { + 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}\""), + _ => return Err(format!("invalid format of \"{name}\"")), } }, - Some(_) => panic!("\"{name}“ should be a quoted float"), - _ => {}, + Some(_) => return Err(format!("\"{name}“ should be a quoted float")), + _ => {}, }; + return Ok(()) } -fn get_string(table: &Table, name: &str) -> Option<String> { +fn get_string(table: &Table, name: &str) -> Result<Option<String>, String> { return match get_value(table, name) { - Some(Value::String(value)) => Some(value.clone()), - Some(_) => panic!("\"{name}\" should be a string"), - _ => None, + Some(Value::String(value)) => Ok(Some(value.clone())), + Some(_) => Err(format!("\"{name}\" should be a string")), + _ => Ok(None), }; } diff --git a/source/benoit/benoit/fractal/from_str.rs b/source/benoit/benoit/fractal/from_str.rs index 151fc98..05cf22e 100644 --- a/source/benoit/benoit/fractal/from_str.rs +++ b/source/benoit/benoit/fractal/from_str.rs @@ -32,12 +32,12 @@ impl FromStr for FractalKind { use FractalKind::*; let kind = match string { - "burningship" => Some(BurningShip), - "mandelbrot" => Some(Mandelbrot), - "multibrot3" => Some(Multibrot3), - "multibrot4" => Some(Multibrot4), - "tricorn" => Some(Tricorn), - _ => None, + "burning_ship" => Some(BurningShip), + "mandelbrot" => Some(Mandelbrot), + "multibrot3" => Some(Multibrot3), + "multibrot4" => Some(Multibrot4), + "tricorn" => Some(Tricorn), + _ => None, }; return match kind { diff --git a/source/benoit/benoit/fractal/iterate/burning_ship.rs b/source/benoit/benoit/fractal/iterate/burning_ship.rs index 9e4f029..dae3a12 100644 --- a/source/benoit/benoit/fractal/iterate/burning_ship.rs +++ b/source/benoit/benoit/fractal/iterate/burning_ship.rs @@ -30,15 +30,14 @@ pub fn burning_ship(z: &mut Complex, c: &Complex) { // // z(n+1) = (abs(Re(z(n)))+i*abs(Im(z(n))))^2+c. - z.real.abs_mut(); // abs(a) - z.imag.abs_mut(); // abs(b) - + z.real.abs_mut(); // abs(a) let za_temporary = z.real.clone(); // abs(a) z.real.square_mut(); // abs(a)^2 z.real -= &z.imag * &z.imag; // abs(a)^2-abs(b)^2 z.real += &c.real; // abs(a)^2-abs(b)^2+Re(c) + z.imag.abs_mut(); // abs(b) z.imag *= &za_temporary; // abs(a) z.imag *= 2.0; // 2*abs(a) z.imag += &c.imag; // 2*abs(a)+Im(c) diff --git a/source/benoit/benoit/image/allocate.rs b/source/benoit/benoit/image/allocate.rs index bbdd61b..bfd5a46 100644 --- a/source/benoit/benoit/image/allocate.rs +++ b/source/benoit/benoit/image/allocate.rs @@ -21,21 +21,24 @@ If not, see <https://www.gnu.org/licenses/>. */ +use crate::benoit::configuration::Configuration; use crate::benoit::image::Image; impl Image { #[must_use] - pub fn allocate(width: u32, height: u32) -> Image { + pub fn allocate(width: u32, height: u32) -> Result<Image, String> { + if width < Configuration::MIN_CANVAS_WIDTH || height < Configuration::MIN_CANVAS_WIDTH { return Err(format!("width and height may not be more than {}", Configuration::MIN_CANVAS_WIDTH)) }; + let (canvas_size, overflow) = (height as usize).overflowing_mul(width as usize); - if overflow { panic!("overflow when calculating canvas size") }; + if overflow { return Err("overflow when calculating canvas size".to_string()) }; let data: Vec::<(u8, u8, u8)> = vec![(0x0, 0x0, 0x0); canvas_size]; - return Image { + return Ok(Image { width: width, height: height, data: data, - }; + }); } } diff --git a/source/benoit/benoit/image/colour.rs b/source/benoit/benoit/image/colour.rs index 3eefd09..9349662 100644 --- a/source/benoit/benoit/image/colour.rs +++ b/source/benoit/benoit/image/colour.rs @@ -22,6 +22,7 @@ */ use crate::benoit::colour_data::ColourData; +use crate::benoit::configuration::Configuration; use crate::benoit::image::Image; use crate::benoit::palette::{Palette, PALETTE_DATA_LENGTH}; use crate::benoit::render::Render; @@ -31,10 +32,16 @@ extern crate rayon; use rayon::prelude::*; impl Image { - pub fn colour(&mut self, render: &Render, palette: Palette, new_max_iter_count: u32, colour_range: f32) { - if render.canvas_size() != self.size() { panic!("canvas size mismatch") }; + pub fn colour(&mut self, render: &Render, palette: Palette, new_max_iter_count: u32, colour_range: f32) -> Result<(), String> { + if render.size() != self.size() { return Err(format!("size mismatch - {}*{} (render) vs. {}*{} (image)", render.size().0, render.size().1, self.size().0, self.size().1)) }; - let (fractal, max_iter_count) = render.info().expect("cannot colour before render"); + if new_max_iter_count < Configuration::MIN_MAX_ITER_COUNT { return Err(format!("maximum_iteration_count must be at least {}", Configuration::MIN_MAX_ITER_COUNT)) }; + if colour_range < Configuration::MIN_COLOUR_RANGE { return Err(format!("colour range may not be less than {}", Configuration::MIN_COLOUR_RANGE)) }; + + let (fractal, max_iter_count) = match render.info() { + Some(info) => info, + None => return Err("cannot colour before render".to_string()), + }; let data = ColourData::new( self, @@ -47,8 +54,11 @@ impl Image { self.data.par_iter_mut().for_each(|point| { let index = data.index(point); let (iter_count, dist) = render[index]; + *point = colour_point(&data, iter_count, dist); }); + + return Ok(()); } } diff --git a/source/benoit/benoit/image/dump.rs b/source/benoit/benoit/image/dump.rs index 1e029c4..cd1f1ea 100644 --- a/source/benoit/benoit/image/dump.rs +++ b/source/benoit/benoit/image/dump.rs @@ -30,19 +30,22 @@ use std::fs::{File, write}; use std::io::BufWriter; impl Image { - pub fn dump(&self, path: &str, format: ImageFormat) { + pub fn dump(&self, path: &str, format: ImageFormat) -> Result<(), String> { use ImageFormat::*; - match format { + return match format { Png => self.dump_png( path), Webp => self.dump_webp(path), - } + }; } - fn dump_png(&self, path: &str) { + fn dump_png(&self, path: &str) -> Result<(), String> { let path = path.to_owned() + ".png"; - let file = File::create(path).expect("unable to create file"); + let file = match File::create(path) { + Ok( file) => file, + Err(_) => return Err("unable to create file".to_string()), + }; let file_buffer = BufWriter::new(file); let mut encoder = png::Encoder::new(file_buffer, self.width, self.height); @@ -51,17 +54,23 @@ impl Image { encoder.set_compression(png::Compression::Fast); encoder.set_srgb(png::SrgbRenderingIntent::Perceptual); - let mut writer = encoder.write_header().expect("unable to write image"); - writer.write_image_data(self.raw()).expect("unable to write image"); + let mut writer = match encoder.write_header() { + Ok( writer) => writer, + Err(_) => return Err("unable to write image header".to_string()), + }; + if writer.write_image_data(self.raw()).is_err() { return Err("unable to write image".to_string()) }; + + return Ok(()); } - fn dump_webp(&self, path: &str) { + fn dump_webp(&self, path: &str) -> Result<(), String> { let path = path.to_owned() + ".webp"; let encoder = webp::Encoder::from_rgb(self.raw(), self.width, self.height); + let data = encoder.encode_lossless(); - let data = encoder.encode_lossless(); + if write(path, &*data).is_err() { return Err("unable to write image".to_string()) }; - write(path, &*data).expect("unable to write image"); + return Ok(()); } } diff --git a/source/benoit/benoit/launcher.rs b/source/benoit/benoit/launcher.rs index 3a5ea48..f38b6c3 100644 --- a/source/benoit/benoit/launcher.rs +++ b/source/benoit/benoit/launcher.rs @@ -21,6 +21,8 @@ If not, see <https://www.gnu.org/licenses/>. */ +use crate::benoit::configuration::Configuration; + pub mod parse_arguments; pub mod print_help; pub mod print_message; @@ -28,6 +30,11 @@ pub mod run; pub mod set_title; pub mod setup; +pub enum Mode { + App( u32, u32), + Script(Configuration), +} + pub struct Launcher {} impl Launcher { diff --git a/source/benoit/benoit/launcher/parse_arguments.rs b/source/benoit/benoit/launcher/parse_arguments.rs index 73d2d79..75adee1 100644 --- a/source/benoit/benoit/launcher/parse_arguments.rs +++ b/source/benoit/benoit/launcher/parse_arguments.rs @@ -22,27 +22,62 @@ */ use crate::benoit::configuration::Configuration; -use crate::benoit::launcher::Launcher; +use crate::benoit::launcher::{Launcher, Mode}; use std::env::args; +use std::str::FromStr; impl Launcher { #[must_use] - pub(super) fn parse_arguments(&self) -> (Configuration, bool) { - let mut arguments = args(); + pub(super) fn parse_arguments(&self) -> Result<Mode, String> { + let arguments = args(); - return match arguments.nth(0x1) { - Some(argument) => { - if argument == "--help" { Launcher::print_help() }; + let mut width: Option<u32> = None; + let mut height: Option<u32> = None; + let mut configuration: Option<Configuration> = None; - let configuration = match Configuration::load(argument.as_str()) { - Ok( configuration) => configuration, - Err(message) => panic!("error parsing configuration: {message}"), - }; + let mut command: Option<String> = None; - (configuration, false) - }, - _ => (Configuration::default(), true), + for argument in arguments.skip(0x1) { + match command { + Some(_) => { + match command.take().unwrap().as_str() { + "height" => height = Some(u32::from_str(argument.as_str()).unwrap()), + "width" => width = Some(u32::from_str(argument.as_str()).unwrap()), + "path" => configuration = Some(Configuration::load(argument.as_str())?), + _ => {}, + } + continue; + }, + _ => {}, + } + + // Check if command doesn't take a value. + match argument.as_str() { + "help" => Launcher::print_help(), + _ => command = Some(argument.clone()), + }; + } + + if command.is_some() { return Err(format!("missing value to command \"{}\"", command.unwrap())) }; + + return if configuration.is_some() { + if width.is_some() || height.is_some() { eprintln!("width and height will be overwritten in script mode") }; + + Ok(Mode::Script(configuration.unwrap())) + } else { + // If only one length is specified, the other is + // assumed equal. + + let width_val = if let Some(val) = width { val } + else if let Some(val) = height { val } + else { 0x100 }; + + let height_val = if let Some(val) = height { val } + else if let Some(val) = width { val } + else { 0xC0 }; + + Ok(Mode::App(width_val, height_val)) }; } } diff --git a/source/benoit/benoit/launcher/print_help.rs b/source/benoit/benoit/launcher/print_help.rs index 4d03053..13d6805 100644 --- a/source/benoit/benoit/launcher/print_help.rs +++ b/source/benoit/benoit/launcher/print_help.rs @@ -37,25 +37,26 @@ impl Launcher { println!(" inverse false|true"); println!(" julia false|true"); println!(); - println!(" canvas_width 0..=4294967295"); - println!(" canvas_height 0..=4294967295"); - println!(" frame_start 0..=4294967295"); - println!(" frame_stop frame_start..=4294967295"); + println!(" canvas_width 2..=4294967295"); + println!(" canvas_height 2..=4294967295"); + println!(" start_frame 0..=4294967295"); + println!(" stop_frame start_frame..=4294967295"); + println!(" palette \"emerald\"|\"fire\"|\"greyscale\"|\"hsv\"|\"lch\"|\"ruby\"|\"sapphire\"|\"simple\"|\"twilight\""); + println!(); + println!(" dump_path"); + println!(" image_format \"png\"|\"webp\""); + println!(); + println!("Additionally, the following parameters may be set in the \"start\" and \"stop\" sections:"); println!(); println!(" real \"0.0\"\u{B1}"); println!(" imaginary \"0.0\"\u{B1}"); println!(" extra_real \"0.0\"\u{B1}"); println!(" extra_imaginary \"0.0\"\u{B1}"); println!(" zoom \"0.0\"<"); - println!(); println!(" maximum_iteration_count 1..=4294967295"); - println!(); - println!(" palette \"emerald\"|\"fire\"|\"greyscale\"|\"hsv\"|\"lch\"|\"ruby\"|\"sapphire\"|\"simple\"|\"twilight\""); println!(" colour_range 2.0+"); println!(); - println!(" dump_path"); - println!(" image_format \"png\"|\"webp\""); - println!(); + println!("If not animating (stop_frame equals zero), only the latter section is used."); println!("Note that real, imaginary, extra_real, extra_imaginary, and zoom - if present - must be quoted floats."); println!("When choosing image format, keep in mind that PNG is faster whilst WebP yields better compression. Both are, however, lossless."); println!(); diff --git a/source/benoit/benoit/launcher/run.rs b/source/benoit/benoit/launcher/run.rs index f949e6a..ca71143 100644 --- a/source/benoit/benoit/launcher/run.rs +++ b/source/benoit/benoit/launcher/run.rs @@ -22,39 +22,36 @@ */ use crate::benoit::app::App; -use crate::benoit::launcher::Launcher; +use crate::benoit::launcher::{Launcher, Mode}; use crate::benoit::script::Script; -use std::thread::available_parallelism; - impl Launcher { #[must_use] - pub fn run(self) -> i32 { + pub fn run(self) -> Result<(), String> { Launcher::print_message(); - let (mut configuration, interative) = self.parse_arguments(); + let mode = self.parse_arguments()?; - configuration.thread_count = if configuration.thread_count == 0x0 { - match available_parallelism() { - Ok(ammount) => ammount.get() as u32, - _ => 0x2, // We assume at least two threads. - } - } else { - configuration.thread_count + let thread_count = match &mode { + Mode::Script(configuration) => configuration.thread_count, + _ => 0x0, }; - self.setup(configuration.thread_count); + self.setup(thread_count); - return if interative { - eprintln!("running in iteractive mode"); + return match mode { + Mode::App(width, height) => { + eprintln!("running in iteractive mode"); - let app = App::configure(configuration); - app.run() - } else { - eprintln!("running in script mode"); + let app = App::new(width, height); + app.run() + }, + Mode::Script(configuration) => { + eprintln!("running in script mode"); - let script = Script::configure(configuration); - script.run() + let script = Script::configure(configuration); + script.run() + }, }; } } diff --git a/source/benoit/benoit/launcher/setup.rs b/source/benoit/benoit/launcher/setup.rs index 3e9be56..da360dd 100644 --- a/source/benoit/benoit/launcher/setup.rs +++ b/source/benoit/benoit/launcher/setup.rs @@ -28,6 +28,7 @@ use crate::benoit::palette::fill_palettes; extern crate rayon; use rayon::ThreadPoolBuilder; +use std::thread::available_parallelism; impl Launcher { pub(super) fn setup(&self, thread_count: u32) { @@ -35,6 +36,15 @@ impl Launcher { fill_palettes(); + let thread_count = if thread_count == 0x0 { + match available_parallelism() { + Ok(ammount) => ammount.get() as u32, + _ => 0x2, // We assume at least two threads. + } + } else { + thread_count + }; + eprintln!("using {} threads", thread_count); ThreadPoolBuilder::new().num_threads(thread_count as usize).build_global().unwrap(); } diff --git a/source/benoit/benoit/render.rs b/source/benoit/benoit/render.rs index 0141f52..9bd3756 100644 --- a/source/benoit/benoit/render.rs +++ b/source/benoit/benoit/render.rs @@ -29,8 +29,8 @@ pub mod render; use std::ops::{Index, IndexMut}; pub struct Render { - canvas_width: u32, - canvas_height: u32, + width: u32, + height: u32, info: Option<(Fractal, u32)>, @@ -39,8 +39,8 @@ pub struct Render { impl Render { #[must_use] - pub fn canvas_size(&self) -> (u32, u32) { - return (self.canvas_width, self.canvas_height); + pub fn size(&self) -> (u32, u32) { + return (self.width, self.height); } #[must_use] diff --git a/source/benoit/benoit/render/allocate.rs b/source/benoit/benoit/render/allocate.rs index 4228914..8e9143d 100644 --- a/source/benoit/benoit/render/allocate.rs +++ b/source/benoit/benoit/render/allocate.rs @@ -21,26 +21,25 @@ If not, see <https://www.gnu.org/licenses/>. */ +use crate::benoit::configuration::Configuration; use crate::benoit::render::Render; impl Render { - pub fn allocate(canvas_width: u32, canvas_height: u32) -> Render { - let canvas_size = { - let (canvas_size, overflow) = canvas_height.overflowing_mul(canvas_width); - if overflow { panic!("overflow when calculating canvas size") }; + pub fn allocate(width: u32, height: u32) -> Result<Render, String> { + if width < Configuration::MIN_CANVAS_WIDTH || height < Configuration::MIN_CANVAS_WIDTH { return Err(format!("width and height may not be more than {}", Configuration::MIN_CANVAS_WIDTH)) }; - canvas_size as usize - }; + let (canvas_size, overflow) = (height as usize).overflowing_mul(width as usize); + if overflow { return Err("overflow when calculating canvas size".to_string()) }; let data: Vec<(u32, f32)> = vec![(0x0, 0.0); canvas_size]; - return Render { - canvas_width: canvas_width, - canvas_height: canvas_height, + return Ok(Render { + width: width, + height: height, info: None, data: data, - }; + }); } } diff --git a/source/benoit/benoit/render/render.rs b/source/benoit/benoit/render/render.rs index 21a86a9..4d11d14 100644 --- a/source/benoit/benoit/render/render.rs +++ b/source/benoit/benoit/render/render.rs @@ -23,6 +23,7 @@ use crate::benoit::{BAILOUT_DISTANCE, PRECISION}; use crate::benoit::complex::Complex; +use crate::benoit::configuration::Configuration; use crate::benoit::fractal::{Fractal, IteratorFunction}; use crate::benoit::render::Render; use crate::benoit::render_data::RenderData; @@ -42,13 +43,14 @@ impl Render { zoom: &Float, extra: &Complex, max_iter_count: u32, - ) { - assert!(max_iter_count > 0x0); + ) -> Result<(), String> { + if *zoom <= 0x0 { return Err("zoom may not be negative nor zero".to_string()) }; + if max_iter_count < Configuration::MIN_MAX_ITER_COUNT { return Err(format!("maximum_iteration_count must be at least {}", Configuration::MIN_MAX_ITER_COUNT)) }; let data = RenderData::new( &mut self.data[..], - self.canvas_width, - self.canvas_height, + self.width, + self.height, centre.clone(), extra.clone(), zoom.clone(), @@ -65,6 +67,8 @@ impl Render { }); self.info = Some((fractal, max_iter_count)); + + return Ok(()); } } diff --git a/source/benoit/benoit/script.rs b/source/benoit/benoit/script.rs index 2b1a7d0..fd34ab2 100644 --- a/source/benoit/benoit/script.rs +++ b/source/benoit/benoit/script.rs @@ -36,24 +36,27 @@ pub mod dump_frame; pub mod run; pub mod still; +pub struct Keyframe { + frame: u32, + centre: Complex, + extra: Complex, + zoom: Float, + max_iter_count: u32, + colour_range: f32, +} + pub struct Script { // Configuration: fractal: Fractal, canvas_width: u32, canvas_height: u32, - frame_start: u32, - frame_stop: u32, - centre: Complex, - extra: Complex, - zoom: Float, - - max_iter_count: u32, - - palette: Palette, - colour_range: f32, + palette: Palette, dump_path: String, image_format: ImageFormat, + + start: Keyframe, + stop: Keyframe, } diff --git a/source/benoit/benoit/script/animate.rs b/source/benoit/benoit/script/animate.rs index 8745090..d30ac8c 100644 --- a/source/benoit/benoit/script/animate.rs +++ b/source/benoit/benoit/script/animate.rs @@ -33,31 +33,43 @@ use rug::ops::PowAssign; impl Script { #[must_use] - pub(super) fn animate(&self) -> i32 { - let frame_count = self.frame_stop - self.frame_start + 0x1; - - let mut render = Render::allocate(self.canvas_width, self.canvas_height); - let mut image = Image::allocate( self.canvas_width, self.canvas_height); - - // zoom_start: - let mut zoom = Float::with_val(PRECISION, 1.0 / 4.0); - - let zoom_stop = Float::with_val(PRECISION, &self.zoom); - - let zoom_factor = get_zoom_factor(&zoom, &zoom_stop, self.frame_stop); - - zoom = if self.frame_start > 0x0 { - let mut zoom = zoom_factor.clone(); - zoom.pow_assign(frame_count); - - zoom - } else { - zoom - }; - - eprintln!("animating from #{} to #{} ({} frame(s)) at {}{:+}i to {:.3}x (fac. ~{:.3})", self.frame_start, self.frame_stop, frame_count + 0x1, self.centre.real.to_f32(), self.centre.imag.to_f32(), zoom_stop.to_f32(), zoom_factor.to_f32()); - - for frame in 0x0..=frame_count { + pub(super) fn animate(&self) -> Result<(), String> { + // TO-DO: Proper animation for centre value when zooming. + + let frame_count = self.stop.frame - self.start.frame + 0x1; + assert!(frame_count >= 0x2); + + let mut render = Render::allocate(self.canvas_width, self.canvas_height)?; + let mut image = Image::allocate( self.canvas_width, self.canvas_height)?; + + // Variables: + let mut centre = self.start.centre.clone(); + let mut extra = self.start.extra.clone(); + let mut zoom = self.start.zoom.clone(); + let mut max_iter_count = self.start.max_iter_count as f32; + let mut colour_range = self.start.colour_range; + + // Steps & factors: + let centre_real_step = get_step_bigfloat(self.stop.frame, &self.start.centre.real, &self.stop.centre.real); + let centre_imag_step = get_step_bigfloat(self.stop.frame, &self.start.centre.imag, &self.stop.centre.imag); + let extra_real_step = get_step_bigfloat(self.stop.frame, &self.start.extra.real, &self.stop.extra.real); + let extra_imag_step = get_step_bigfloat(self.stop.frame, &self.start.extra.imag, &self.stop.extra.imag); + let zoom_factor = get_factor_bigfloat(self.stop.frame, &self.start.zoom, &self.stop.zoom); + let max_iter_count_step = get_step(self.stop.frame, self.start.max_iter_count as f32, self.stop.max_iter_count as f32); + let colour_range_step = get_step(self.stop.frame, self.start.colour_range, self.stop.colour_range); + + eprintln!(""); + eprintln!("animating {frame_count} frames: the {}", self.fractal.name()); + eprintln!(" re(c): {} \u{2192} {} (step: {centre_real_step})", self.start.centre.real, self.stop.centre.real); + eprintln!(" im(c): {} \u{2192} {} (step: {centre_imag_step})", self.start.centre.imag, self.stop.centre.imag); + eprintln!(" re(w): {} \u{2192} {} (step: {extra_real_step})", self.start.extra.real, self.stop.extra.real); + eprintln!(" im(w): {} \u{2192} {} (step: {extra_imag_step})", self.start.extra.imag, self.stop.extra.imag); + eprintln!(" zoom: {} \u{2192} {} (fac.: {zoom_factor})", self.start.zoom, self.stop.zoom); + eprintln!(" max. iter count: {} \u{2192} {} (step: {max_iter_count_step})", self.start.max_iter_count, self.stop.max_iter_count); + eprintln!(" col. range: {} \u{2192} {} (step: {colour_range_step})", self.start.colour_range, self.stop.colour_range); + eprintln!(""); + + for frame in 0x0..frame_count { let frame_name = format!("frame{frame:010}"); Script::dump_frame( @@ -67,28 +79,71 @@ impl Script { &mut render, self.fractal, self.palette, - &self.centre, - &self.extra, + ¢re, + &extra, &zoom, - self.max_iter_count, - self.colour_range, + max_iter_count.round() as u32, + colour_range, self.image_format, - ); - - zoom *= &zoom_factor; + )?; + + centre.real += ¢re_real_step; + centre.imag += ¢re_imag_step; + extra.real += &extra_real_step; + extra.imag += &extra_imag_step; + zoom *= &zoom_factor; + max_iter_count += max_iter_count_step; + colour_range += colour_range_step; } - return 0x0; + return Ok(()); } } -fn get_zoom_factor(zoom_start: &Float, zoom_stop: &Float, frame_count: u32) -> Float { - assert!(frame_count > 0x0); +#[must_use] +fn get_step(stop_x: u32, start_y: f32, stop_y: f32) -> f32 { + assert!(stop_x - START_X != 0x0); + + const START_X: u32 = 0x1; + + // a = (y1-y0)/(x1-x0) + return (stop_y - start_y) / (stop_x as f32 - START_X as f32); +} + +#[allow(dead_code)] +#[must_use] +fn get_factor(stop_x: u32, start_y: f32, stop_y: f32) -> f32 { + assert!(stop_x - START_X != 0x0); + + const START_X: u32 = 0x1; + + // a = (y1/y0)^(1/(x1-x0)) + return (stop_y / start_y).powf(1.0 / (stop_x as f32 - START_X as f32)); +} + +#[must_use] +fn get_step_bigfloat(stop_x: u32, start_y: &Float, stop_y: &Float) -> Float { + assert!(stop_x - START_X > 0x0); + + const START_X: u32 = 0x1; + + let numerator = Float::with_val(PRECISION, stop_y - start_y); + let denominator = stop_x - START_X; + + return numerator / denominator; +} + +#[must_use] +fn get_factor_bigfloat(stop_x: u32, start_y: &Float, stop_y: &Float) -> Float { + assert!(stop_x - START_X > 0x0); + if stop_y == start_y { return Float::with_val(PRECISION, 0x1) }; + + const START_X: u32 = 0x1; // To get the zoom factor, we first want the 'a' // value of the growth function from (0) to // (frame_count) on the x-dimension and from - // (zoom_start) to (zoom_stop) on the y-dimension: + // (zoom_start) to (stop_zoom) on the y-dimension: // // a = nroot(x1-x0,y1/y0), // @@ -99,14 +154,11 @@ fn get_zoom_factor(zoom_start: &Float, zoom_stop: &Float, frame_count: u32) -> F // // making the final equation // - // (y1/y0)^(1/(x1-x0)) = (zoom_stop/zoom_start)^(1/(frame_count-1)). - - let frame_start: f32 = 0.0; - let frame_stop: f32 = frame_count as f32; + // (y1/y0)^(1/(x1-x0)) = (stop_zoom/zoom_start)^(1/(frame_count-1)). - let exponent = Float::with_val(PRECISION, 1.0 / (frame_stop - frame_start)); + let exponent = 1.0 / (stop_x as f64 - START_X as f64); - let mut factor = Float::with_val(PRECISION, zoom_stop / zoom_start); + let mut factor = Float::with_val(PRECISION, stop_y / start_y); factor.pow_assign(exponent); return factor; diff --git a/source/benoit/benoit/script/configure.rs b/source/benoit/benoit/script/configure.rs index 9966199..be2e1cd 100644 --- a/source/benoit/benoit/script/configure.rs +++ b/source/benoit/benoit/script/configure.rs @@ -23,7 +23,7 @@ use crate::benoit::complex::Complex; use crate::benoit::configuration::Configuration; -use crate::benoit::script::Script; +use crate::benoit::script::{Keyframe, Script}; impl Script { #[must_use] @@ -33,21 +33,28 @@ impl Script { canvas_width: configuration.canvas_width, canvas_height: configuration.canvas_height, - frame_start: configuration.frame_start, - frame_stop: configuration.frame_stop, - - centre: Complex::new(configuration.centre_real, configuration.centre_imag), - zoom: configuration.zoom, - - extra: Complex::new(configuration.extra_real, configuration.extra_imag), - - max_iter_count: configuration.max_iter_count, - - palette: configuration.palette, - colour_range: configuration.colour_range, + palette: configuration.palette, dump_path: configuration.dump_path, image_format: configuration.image_format, + + start: Keyframe { + frame: configuration.start_frame, + centre: Complex::new(configuration.start_centre_real, configuration.start_centre_imag), + extra: Complex::new(configuration.start_extra_real, configuration.start_extra_imag), + zoom: configuration.start_zoom, + max_iter_count: configuration.start_max_iter_count, + colour_range: configuration.start_colour_range, + }, + + stop: Keyframe { + frame: configuration.stop_frame, + centre: Complex::new(configuration.stop_centre_real, configuration.stop_centre_imag), + extra: Complex::new(configuration.stop_extra_real, configuration.stop_extra_imag), + zoom: configuration.stop_zoom, + max_iter_count: configuration.stop_max_iter_count, + colour_range: configuration.stop_colour_range, + }, }; } } diff --git a/source/benoit/benoit/script/dump_frame.rs b/source/benoit/benoit/script/dump_frame.rs index c3f1f46..86b62c6 100644 --- a/source/benoit/benoit/script/dump_frame.rs +++ b/source/benoit/benoit/script/dump_frame.rs @@ -48,8 +48,8 @@ impl Script { max_iter_count: u32, colour_range: f32, image_format: ImageFormat, - ) { - eprint!("\"{name}\" (2^{:.9}x): rendering...", zoom.to_f64().log2()); + ) -> Result<(), String> { + eprint!("\u{1B}[1m\u{1B}[94mX\u{1B}[0m \"{name}\" (2^{:.9}x): rendering...", zoom.to_f64().log2()); Launcher::set_title("Rendering..."); let time_start = Instant::now(); @@ -60,23 +60,25 @@ impl Script { zoom, extra, max_iter_count, - ); + )?; let render_time = time_start.elapsed(); eprint!(" {:.3}ms, colouring...", render_time.as_micros() as f32 / 1000.0); Launcher::set_title("Colouring..."); - image.colour(&render, palette, max_iter_count, colour_range); + image.colour(&render, palette, max_iter_count, colour_range)?; let colour_time = time_start.elapsed() - render_time; eprint!(" {:.3}ms, dumping...", colour_time.as_micros() as f32 / 1000.0); Launcher::set_title("Dumping..."); let path = format!("{dump_path}/{name}"); - image.dump(path.as_str(), image_format); + image.dump(path.as_str(), image_format)?; let dump_time = time_start.elapsed() - colour_time - render_time; - eprintln!(" {:.3}ms - \u{1B}[1m\u{1B}[92mdone\u{1B}[0m", dump_time.as_micros() as f32 / 1000.0); - Launcher::set_title("Done"); + eprintln!(" {:.3}ms\r\u{1B}[1m\u{1B}[92mO\u{1B}[0m", dump_time.as_micros() as f32 / 1000.0); + Launcher::set_title("Done, waiting..."); + + return Ok(()); } } diff --git a/source/benoit/benoit/script/run.rs b/source/benoit/benoit/script/run.rs index 24a34b0..5a0f4c7 100644 --- a/source/benoit/benoit/script/run.rs +++ b/source/benoit/benoit/script/run.rs @@ -25,12 +25,10 @@ use crate::benoit::script::Script; impl Script { #[must_use] - pub fn run(self) -> i32 { - let code = match self.frame_stop == 0x0 && self.frame_start == 0x0 { + pub fn run(self) -> Result<(), String> { + return match self.stop.frame == 0x0 && self.start.frame == 0x0 { false => self.animate(), true => self.still(), }; - - return code; } } diff --git a/source/benoit/benoit/script/still.rs b/source/benoit/benoit/script/still.rs index 8da69ca..ee93b65 100644 --- a/source/benoit/benoit/script/still.rs +++ b/source/benoit/benoit/script/still.rs @@ -27,12 +27,23 @@ use crate::benoit::script::Script; impl Script { #[must_use] - pub(super) fn still(&self) -> i32 { - let mut render = Render::allocate(self.canvas_width, self.canvas_height); - let mut image = Image::allocate( self.canvas_width, self.canvas_height); + pub(super) fn still(&self) -> Result<(), String> { + let mut render = Render::allocate(self.canvas_width, self.canvas_height)?; + let mut image = Image::allocate( self.canvas_width, self.canvas_height)?; const FRAME_NAME: &str = "render"; + eprintln!(""); + eprintln!("rendering still: the {}", self.fractal.name()); + eprintln!(" re(c): {}", self.stop.centre.real); + eprintln!(" im(c): {}", self.stop.centre.imag); + eprintln!(" re(w): {}", self.stop.extra.real); + eprintln!(" im(w): {}", self.stop.extra.imag); + eprintln!(" zoom: {}", self.stop.zoom); + eprintln!(" max. iter count: {}", self.stop.max_iter_count); + eprintln!(" col. range: {}", self.stop.colour_range); + eprintln!(""); + Script::dump_frame( self.dump_path.as_str(), FRAME_NAME, @@ -40,14 +51,14 @@ impl Script { &mut render, self.fractal, self.palette, - &self.centre, - &self.extra, - &self.zoom, - self.max_iter_count, - self.colour_range, + &self.stop.centre, + &self.stop.extra, + &self.stop.zoom, + self.stop.max_iter_count, + self.stop.colour_range, self.image_format, - ); + )?; - return 0x0; + return Ok(()); } } diff --git a/source/benoit/benoit/video/initialise.rs b/source/benoit/benoit/video/initialise.rs index 64a0608..f78ee7a 100644 --- a/source/benoit/benoit/video/initialise.rs +++ b/source/benoit/benoit/video/initialise.rs @@ -31,18 +31,31 @@ use sdl2::render::BlendMode; impl Video { #[must_use] - pub fn initialise(canvas_width: u32, canvas_height: u32, scale: u32) -> Video { - let sdl = sdl2::init().expect("unable to initialise sdl2"); - let sdl_video = sdl.video().expect("unable to initialise video"); + pub fn initialise(canvas_width: u32, canvas_height: u32, scale: u32) -> Result<Video, String> { + let sdl = match sdl2::init() { + Ok( sdl) => sdl, + Err(_) => return Err("unable to initialise sdl2".to_string()), + }; + + let sdl_video = match sdl.video() { + Ok( video) => video, + Err(_) => return Err("unable to initialise video".to_string()), + }; let window_title = format!("BENO\u{CE}T {:X}.{:X}.{:X}", VERSION.0, VERSION.1, VERSION.2); let mut window_builder = sdl_video.window(window_title.as_str(), canvas_width * scale, canvas_height * scale); window_builder.position_centered(); - let window = window_builder.build().expect("unable to open window"); + let window = match window_builder.build() { + Ok( window) => window, + Err(_) => return Err("unable to open window".to_string()), + }; - let mut canvas = window.into_canvas().build().expect("unable to create canvas"); + let mut canvas = match window.into_canvas().build() { + Ok( canvas) => canvas, + Err(_) => return Err("unable to build canvas".to_string()), + }; canvas.set_blend_mode(BlendMode::Blend); @@ -54,10 +67,10 @@ impl Video { canvas.clear(); canvas.present(); - return Video { + return Ok(Video { sdl: sdl, sdl_video: sdl_video, canvas: canvas, - }; + }); } } diff --git a/source/benoit/benoit/video/sync.rs b/source/benoit/benoit/video/sync.rs index c8c016a..46112eb 100644 --- a/source/benoit/benoit/video/sync.rs +++ b/source/benoit/benoit/video/sync.rs @@ -27,20 +27,28 @@ use std::thread::sleep; use std::time::{Duration, Instant}; impl Video { - pub fn sync(&self, frame_start: &Instant) { + pub fn sync(&self, start_frame: &Instant) -> Result<(), String> { let frame_duration = { - let index = self.canvas.window().display_index().expect("unable to get display index"); + let index = match self.canvas.window().display_index() { + Ok( index) => index, + Err(_) => return Err("unable to get display index".to_string()), + }; - let mode = self.sdl_video.current_display_mode(index).expect("unable to get display mode"); + let mode = match self.sdl_video.current_display_mode(index) { + Ok( mode) => mode, + Err(_) => return Err("unable to get display mode".to_string()), + }; Duration::from_secs(0x1) / mode.refresh_rate as u32 }; - let remaining = match frame_duration.checked_sub(frame_start.elapsed()) { + let remaining = match frame_duration.checked_sub(start_frame.elapsed()) { Some(value) => value, None => Duration::from_secs(0x0), }; sleep(remaining); + + return Ok(()); } }
\ No newline at end of file diff --git a/source/benoit/main.rs b/source/benoit/main.rs index 42822d3..1264873 100644 --- a/source/benoit/main.rs +++ b/source/benoit/main.rs @@ -29,7 +29,14 @@ use std::process::exit; fn main() { let launcher = Launcher::new(); - let code = launcher.run(); + let result = launcher.run(); + + if let Err(ref error) = result { eprintln!("error: {error}") }; + + let code: i32 = match result { + Ok( _) => 0x0, + Err(_) => 0x1, + }; exit(code); } |