mandelbrot p1

This commit is contained in:
Sangeeth Sudheer 2022-08-17 01:46:30 +05:30
parent dd6cdbee6d
commit 9fcfb464c1
3 changed files with 196 additions and 0 deletions

1
mandelbrot/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
mandelbrotimage.png

10
mandelbrot/Cargo.toml Normal file
View File

@ -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"

185
mandelbrot/src/main.rs Normal file
View File

@ -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<f64>, limit: usize) -> Option<usize> {
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 "<left><sep><right>" and <sep> should be an ASCII
/// character. <left> and <right> must be parseable by T::from_str.
///
/// Returns `None` if the string cannot be parsed, otherwise, returns a pair containing the parsed
/// <left> and <right> values.
fn parse_pair<T: FromStr>(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::<u64>("", ','), None);
assert_eq!(parse_pair::<u64>(",", ','), None);
assert_eq!(parse_pair::<u64>("3,", ','), None);
assert_eq!(parse_pair::<u64>(",4", ','), None);
assert_eq!(parse_pair::<u64>("300xG", 'x'), None);
assert_eq!(parse_pair::<u64>("300x400x", 'x'), None);
assert_eq!(parse_pair::<u64>("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<Complex<f64>> {
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<f64>,
lower_right: Complex<f64>,
) -> Complex<f64> {
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<f64>,
lower_right: Complex<f64>,
) {
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<String> = 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]);
}