commit 9f7abe4761fb0464ad3b1712449e3f1d681e9704 Author: h7x4 Date: Sun Mar 9 18:20:39 2025 +0100 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100755 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100755 index 0000000..cc36f65 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,405 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "adler32" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234" + +[[package]] +name = "autocfg" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "byteorder" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "color_quant" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" + +[[package]] +name = "crossbeam" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ae5588f6b3c3cb05239e90bd110f257254aecd01e4635400391aeae07497845" +dependencies = [ + "cfg-if", + "crossbeam-channel", + "crossbeam-deque", + "crossbeam-epoch", + "crossbeam-queue", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06ed27e177f16d65f0f0c22a213e17c696ace5dd64b14258b52f9417ccb52db4" +dependencies = [ + "cfg-if", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6455c0ca19f0d2fbf751b908d5c55c1f5cbc65e03c4225427254b46890bdde1e" +dependencies = [ + "cfg-if", + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ec02e091aa634e2c3ada4a392989e7c3116673ef0ac5b72232439094d73b7fd" +dependencies = [ + "cfg-if", + "crossbeam-utils", + "lazy_static", + "memoffset", + "scopeguard", +] + +[[package]] +name = "crossbeam-queue" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b10ddc024425c88c2ad148c1b0fd53f4c6d38db9697c9f1588381212fa657c9" +dependencies = [ + "cfg-if", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d82cfc11ce7f2c3faef78d8a684447b40d503d9681acebed6cb728d45940c4db" +dependencies = [ + "cfg-if", + "lazy_static", +] + +[[package]] +name = "deflate" +version = "0.7.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "707b6a7b384888a70c8d2e8650b3e60170dfc6a67bb4aa67b6dfca57af4bedb4" +dependencies = [ + "adler32", + "byteorder", +] + +[[package]] +name = "either" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" + +[[package]] +name = "gif" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "471d90201b3b223f3451cd4ad53e34295f16a1df17b1edf3736d47761c3981af" +dependencies = [ + "color_quant", + "lzw", +] + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "image" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebdff791af04e30089bde8ad2a632b86af433b40c04db8d70ad4b21487db7a6a" +dependencies = [ + "byteorder", + "gif", + "jpeg-decoder", + "lzw", + "num-derive", + "num-iter", + "num-rational 0.1.42", + "num-traits", + "png", + "scoped_threadpool", +] + +[[package]] +name = "inflate" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cdb29978cc5797bd8dcc8e5bf7de604891df2a8dc576973d71a281e916db2ff" +dependencies = [ + "adler32", +] + +[[package]] +name = "jpeg-decoder" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "229d53d58899083193af11e15917b5640cd40b29ff475a1fe4ef725deb02d0f2" +dependencies = [ + "rayon", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.105" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "869d572136620d55835903746bcb5cdc54cb2851fd0aeec53220b4bb65ef3013" + +[[package]] +name = "lzw" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d947cbb889ed21c2a84be6ffbaebf5b4e0f4340638cba0444907e38b56be084" + +[[package]] +name = "mandelbrot" +version = "0.1.0" +dependencies = [ + "crossbeam", + "image", + "num", +] + +[[package]] +name = "memoffset" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59accc507f1338036a0477ef61afdae33cde60840f4dfe481319ce3ad116ddf9" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43db66d1170d347f9a065114077f7dccb00c1b9478c89384490a3425279a4606" +dependencies = [ + "num-bigint", + "num-complex", + "num-integer", + "num-iter", + "num-rational 0.4.0", + "num-traits", +] + +[[package]] +name = "num-bigint" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74e768dff5fb39a41b3bcd30bb25cf989706c90d028d1ad71971987aa309d535" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-complex" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26873667bbbb7c5182d4a37c1add32cdf09f841af72da53318fdb81543c15085" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-derive" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eafd0b45c5537c3ba526f79d3e75120036502bebacbb3f3220914067ce39dbf2" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "num-integer" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2021c8337a54d21aca0d59a92577a029af9431cb59b909b03252b9c164fad59" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.1.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee314c74bd753fc86b4780aa9475da469155f3848473a261d2d18e35245a784e" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d41702bd167c2df5520b384281bc111a4b5efcf7fbc4c9c222c815b07e0a6a6a" +dependencies = [ + "autocfg", + "num-bigint", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_cpus" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "png" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f54b9600d584d3b8a739e1662a595fab051329eff43f20e7d8cc22872962145b" +dependencies = [ + "bitflags", + "deflate", + "inflate", + "num-iter", +] + +[[package]] +name = "proc-macro2" +version = "0.4.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf3d2011ab5c909338f7887f4fc896d35932e29146c12c8d01da6b22a80ba759" +dependencies = [ + "unicode-xid", +] + +[[package]] +name = "quote" +version = "0.6.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce23b6b870e8f94f81fb0a363d65d86675884b34a09043c81e5562f11c1f8e1" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rayon" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06aca804d41dbc8ba42dfd964f0d01334eceb64314b9ecf7c5fad5188a06d90" +dependencies = [ + "autocfg", + "crossbeam-deque", + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d78120e2c850279833f1dd3582f730c4ab53ed95aeaaaa862a2a5c71b1656d8e" +dependencies = [ + "crossbeam-channel", + "crossbeam-deque", + "crossbeam-utils", + "lazy_static", + "num_cpus", +] + +[[package]] +name = "scoped_threadpool" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d51f5df5af43ab3f1360b429fa5e0152ac5ce8c0bd6485cae490332e96846a8" + +[[package]] +name = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + +[[package]] +name = "syn" +version = "0.15.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ca4b3b69a77cbe1ffc9e198781b7acb0c7365a883670e8f1c1bc66fba79a5c5" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + +[[package]] +name = "unicode-xid" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" diff --git a/Cargo.toml b/Cargo.toml new file mode 100755 index 0000000..8ce19cc --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "mandelbrot" +version = "0.1.0" +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +num = "*" +image = "0.19" +crossbeam = "*" diff --git a/README.md b/README.md new file mode 100644 index 0000000..ee3d723 --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# mandelbrot-rs + +Implementation of mandelbrot renderer in rust, as described in [Programming Rust: Fast, Safe Systems Development](https://www.oreilly.com/library/view/programming-rust-2nd/9781492052586/) diff --git a/mandel.png b/mandel.png new file mode 100755 index 0000000..6907ae2 Binary files /dev/null and b/mandel.png differ diff --git a/mandelbrot.png b/mandelbrot.png new file mode 100755 index 0000000..91229b4 Binary files /dev/null and b/mandelbrot.png differ diff --git a/src/main.rs b/src/main.rs new file mode 100755 index 0000000..8904da2 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,165 @@ +use num::Complex; +use std::str::FromStr; +use image::ColorType; +use image::png::PNGEncoder; +use std::fs::File; +use std::io::Write; +use crossbeam; + +fn escape_time(c: Complex, limit: u32) -> Option { + let mut z = Complex {re: 0.0, im: 0.0}; + for i in 0..limit { + z = z * z + c; + if z.norm_sqr() > 4.0 { + return Some(i); + } + } + + None +} + +fn parse_pair(s: &str, separator: char) -> Option<(T,T)> { + s.find(separator).and_then(|i| { + match (T::from_str(&s[..i]), T::from_str(&s[i + 1..])) { + (Ok(l), Ok(r)) => Some((l,r)), + _ => None + } + + }) +} + +#[test] +fn test_parse_pair() { + assert_eq!(parse_pair::("", ','), None); + assert_eq!(parse_pair::("10,", ','), None); + assert_eq!(parse_pair::(",10", ','), None); + assert_eq!(parse_pair::("10,20", ','), Some((10, 20))); + assert_eq!(parse_pair::("10,20xy", ','), None); + assert_eq!(parse_pair::("0.5x", 'x'), None); + assert_eq!(parse_pair::("0.5x1.5", 'x'), Some((0.5, 1.5))); +} + +fn parse_complex(s: &str) -> Option> { + parse_pair(s, ',').and_then(|(re, im)| Some(Complex {re, im})) + // match parse_pair(s, ',') { + // None => None + // Some((re, im)) + // } +} + +#[test] +fn test_parse_complex() { + assert_eq!(parse_complex("1.25,-0.0625"), + Some(Complex { re: 1.25, im: -0.0625 })); + assert_eq!(parse_complex(",-0.0625"), None); +} + +fn pixel_to_point(bounds: (usize, usize), + pixel: (usize, usize), + upper_left: Complex, + lower_right: Complex) -> Complex { + let (width, height) = (lower_right.re - upper_left.re, + upper_left.im - lower_right.im); + Complex { + re: upper_left.re + pixel.0 as f64 * width / bounds.0 as f64, + im: upper_left.im - pixel.1 as f64 * height / bounds.1 as f64 + } +} + +#[test] +fn test_pixel_to_point() { + assert_eq!(pixel_to_point((100, 200), (25, 175), + Complex { re: -1.0, im: 1.0 }, + Complex { re: 1.0, im: -1.0 }), + Complex { re: -0.5, im: -0.75 }); +} + +fn render(pixels: &mut [u8], + bounds: (usize, usize), + upper_left: Complex, + lower_right: Complex) { + + assert!(pixels.len() == bounds.0 * bounds.1); + + for row in 0 .. bounds.1 { + for column in 0 .. bounds.0 { + let point = pixel_to_point(bounds, (column,row), upper_left, lower_right); + pixels[row * bounds.0 + column] = + match escape_time(point, 255) { + None => 0, + Some(count) => 255 - count as u8 + } + } + } +} + +use std::io::Result; + +fn write_image(filename: &str, pixels: &[u8], bounds: (usize, usize)) -> Result<()> { + let output = File::create(filename)?; + // let output = match File::create(filename) { + // Ok(f) => { f }, + // Err(e) => { return Err(e); } + // } + + let encoder = PNGEncoder::new(output); + + encoder.encode(&pixels, bounds.0 as u32, bounds.1 as u32, ColorType::Gray(8))?; + // match encoder.encode(&pixels, bounds.0 as u32, bounds.1 as u32, ColorType::Gray(8)) { + // Ok(_) => { () }, + // _ => { return Err(std::io::Error::new(std::io::ErrorKind::Other, "oh no!")); } + // }; + + Ok(()) +} + +fn single_threaded_render(pixels: &mut [u8], + bounds: (usize, usize), + upper_left: Complex, + lower_right: Complex) { + render(pixels, bounds, upper_left, lower_right); +} + +fn multi_threaded_render(pixels: &mut [u8], + bounds: (usize, usize), + upper_left: Complex, + lower_right: Complex) { + let threads = 8; + let rows_per_band = bounds.1 / threads + 1; + + let bands: Vec<&mut [u8]> = pixels.chunks_mut(rows_per_band * bounds.0).collect(); + crossbeam::scope(|spawner| { + for (i, band) in bands.into_iter().enumerate() { + let top = rows_per_band * i; + let height = band.len() / bounds.0; + let band_bounds = (bounds.0, height); + let band_upper_left = pixel_to_point(bounds, (0, top), upper_left, lower_right); + let band_lower_right = pixel_to_point(bounds, (bounds.0, top + height), upper_left, lower_right); + + spawner.spawn(move |_| { + render(band, band_bounds, band_upper_left, band_lower_right); + }); + } + }); + +} + +fn main() { + let args: Vec = std::env::args().collect(); + if args.len() != 5 { + writeln!(std::io::stderr(), "Usage: mandelbrot FILE PIXELS UPPERLEFT LOWERRIGHT").unwrap(); + writeln!(std::io::stderr(), "Example: {} mandel.png 1000x750 -1.20,0.35 -1,0.20", args[0]).unwrap(); + std::process::exit(1); + } + + let bounds = parse_pair(&args[2], 'x').expect("error parsing image dimensions"); + let upper_left = parse_complex(&args[3]).expect("error parsing upper left corner point"); + let lower_right = parse_complex(&args[4]).expect("error parsing lower right corner point"); + + let mut pixels = vec![0; bounds.0 * bounds.1]; + + multi_threaded_render(&mut pixels, bounds, upper_left, lower_right); + // single_threaded_render(&mut pixels, bounds, upper_left, lower_right); + + write_image(&args[1], &pixels, bounds).expect("error writing PNG file"); +}