mandelbrot p1
This commit is contained in:
parent
dd6cdbee6d
commit
9fcfb464c1
1
mandelbrot/.gitignore
vendored
Normal file
1
mandelbrot/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
mandelbrotimage.png
|
10
mandelbrot/Cargo.toml
Normal file
10
mandelbrot/Cargo.toml
Normal 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
185
mandelbrot/src/main.rs
Normal 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]);
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user