From 9fcfb464c12c3a8c3d6a96786bbf40bdbec9f554 Mon Sep 17 00:00:00 2001 From: Sangeeth Sudheer Date: Wed, 17 Aug 2022 01:46:30 +0530 Subject: [PATCH] mandelbrot p1 --- mandelbrot/.gitignore | 1 + mandelbrot/Cargo.toml | 10 +++ mandelbrot/src/main.rs | 185 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 196 insertions(+) create mode 100644 mandelbrot/.gitignore create mode 100644 mandelbrot/Cargo.toml create mode 100644 mandelbrot/src/main.rs diff --git a/mandelbrot/.gitignore b/mandelbrot/.gitignore new file mode 100644 index 0000000..33af04d --- /dev/null +++ b/mandelbrot/.gitignore @@ -0,0 +1 @@ +mandelbrotimage.png \ No newline at end of file diff --git a/mandelbrot/Cargo.toml b/mandelbrot/Cargo.toml new file mode 100644 index 0000000..7a18cad --- /dev/null +++ b/mandelbrot/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "mandelbrot" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +num = "0.4" +image = "0.13.0" \ No newline at end of file diff --git a/mandelbrot/src/main.rs b/mandelbrot/src/main.rs new file mode 100644 index 0000000..beb41e0 --- /dev/null +++ b/mandelbrot/src/main.rs @@ -0,0 +1,185 @@ +use image::{png::PNGEncoder, ColorType}; +use num::Complex; +use std::{fs::File, str::FromStr, env, process}; + +/// Check to see if `c` is in the Mandelbrot set, by doing at-most `limit` iterations to decide. +/// +/// If `c` is not a member, return `Some(i)` where `i` is the number of iterations it took for `c` +/// to leave the circle of radius 2 centered on the origin. If `c` seems to be a member (i.e we +/// reached the iteration limit defined by `limit` without being able to prove that `c` is not a +/// member), return `None`. +fn escape_time(c: Complex, limit: usize) -> Option { + let mut z = Complex { re: 0.0, im: 0.0 }; + + for i in 0..limit { + if z.norm_sqr() > 4.0 { + return Some(i); + } + + z = z * z + c; + } + + None +} + +/// Parse the string `s` as a coordinate pair (e.g "300x400" or "1.0,2.0") +/// +/// The string is expected to be in the form "" and should be an ASCII +/// character. and must be parseable by T::from_str. +/// +/// Returns `None` if the string cannot be parsed, otherwise, returns a pair containing the parsed +/// and values. +fn parse_pair(s: &str, seperator: char) -> Option<(T, T)> { + match s.find(seperator) { + Some(index) => match (T::from_str(&s[0..index]), T::from_str(&s[index + 1..])) { + (Ok(left), Ok(right)) => Some((left, right)), + _ => None, + }, + None => None, + } +} + +#[test] +fn test_parse_pair() { + assert_eq!(parse_pair::("", ','), None); + assert_eq!(parse_pair::(",", ','), None); + assert_eq!(parse_pair::("3,", ','), None); + assert_eq!(parse_pair::(",4", ','), None); + assert_eq!(parse_pair::("300xG", 'x'), None); + assert_eq!(parse_pair::("300x400x", 'x'), None); + assert_eq!(parse_pair::("300x400", 'x'), Some((300, 400))); +} + +/// Parses a "real,imag" string as a complex number of the form `real + imag * i` +fn parse_complex(s: &str) -> Option> { + match parse_pair(s, ',') { + Some((re, im)) => Some(Complex { re, im }), + None => None, + } +} + +#[test] +fn test_parse_complex() { + assert_eq!( + parse_complex("1.0,-3.4"), + Some(Complex { re: 1.0, im: -3.4 }) + ); +} + +/// Given the row and column of a pixel, return the corresponding point in the complex plane +/// +/// `pixel` refers to a (column, row) pair representing a pixel. +/// `bounds` refers to (width, height) of the image. +/// `upper_left` and `lower_left` refers to points which form a bounding box in the complex plane +/// that the image represents +fn pixel_to_point( + pixel: (usize, usize), + bounds: (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, + // Subtraction needed since column value of a pixel grid increases as we move down whereas + // the imaginary value increases as we go up in the complex plane + } +} + +#[test] +fn test_pixel_to_point() { + assert_eq!( + pixel_to_point( + (50, 50), + (100, 100), + Complex { re: -1.0, im: 1.0 }, + Complex { re: 1.0, im: -1.0 } + ), + Complex { re: 0.0, im: 0.0 } + ); + assert_eq!( + pixel_to_point( + (25, 175), + (100, 200), + Complex { re: -1.0, im: 1.0 }, + Complex { re: 1.0, im: -1.0 } + ), + Complex { + re: -0.5, + im: -0.75 + } + ); +} + +/// Render a rectangle of the Mandelbrot set into a buffer of pixels +/// +/// `pixels` is a buffer of bytes bounded by the width and height pair in `bounds`, each byte +/// representing a grayscale pixel value. `upper_left` and `lower_right` represent points in the +/// complex plane that maps to the upper-left and lower-right pixels in the pixels buffer +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((column, row), bounds, upper_left, lower_right); + + pixels[row * bounds.0 + column] = match escape_time(point, 255) { + Some(i) => 255 - i as u8, + None => 0, + } + } + } +} + +/// Write the pixel buffer represented by `pixels` into the file named `filename`. +fn write_image( + filename: &str, + pixels: &[u8], + bounds: (usize, usize), +) -> Result<(), std::io::Error> { + let output = File::create(filename)?; + + let encoder = PNGEncoder::new(output); + + encoder.encode( + pixels, + bounds.0 as u32, + bounds.1 as u32, + ColorType::Gray(8), + )?; + + Ok(()) +} + +fn main() { + let args: Vec = env::args().collect(); + + if args.len() != 5 { + eprintln!("Usage: {} FILE PIXELS UPPERLEFT LOWERRIGHT", args[0]); + eprintln!("Example: {} mandelbrot.png 300x400 -1.0,1.0 1.0,-1.0", args[0]); + + process::exit(1); + } + + let bounds: (usize, usize) = parse_pair(&args[2], 'x').expect("error parsing image dimensions"); + let upper_left = parse_complex(&args[3]).expect("error parsing upper-left point"); + let lower_right = parse_complex(&args[4]).expect("error parsing lower-right point"); + + let mut pixels = vec![0 as u8; bounds.0 * bounds.1]; + + render(&mut pixels, bounds, upper_left, lower_right); + + write_image(&args[1], &pixels, bounds).expect("error writing PNG image"); + + println!("Mandelbrot set rendered into {}", args[1]); +}