extern crate crossbeam; extern crate image; extern crate num; use image::ColorType; use image::png::PNGEncoder; use num::Complex; use std::fs::File; use std::io::Write; use std::str::FromStr; /// Parse the string `s` as a coordinate pair, like `"400x600"` or `"1.0,0.5"`. /// /// Specifically, `s` should have the form , where is /// the character given by the `separator` argument, and and are both /// strings that can be parsed by `T::from_str`. /// /// If `s` has the proper form, return `Some<(x, y)>`. If it doesn't parse /// correctly, return `None` fn parse_pair(s: &str, separator: char) -> Option<(T,T)> { match s.find(separator) { None => None, Some(index) => { match (T::from_str(&s[..index]), T::from_str(&s[index + 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,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))); } /// Try to determine if `c` is in the Mandelbrot set, using 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 two centered on the /// origin. If `c` seems to be a member (more precisely, if we reached the /// iteration limit without being able to prove that `c` is not a member), /// return `None` 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 } /// Parse a pair of floating-point numbers separated by a comma as a complex /// number 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.25,-0.0625"), Some(Complex { re: 1.25, im: -0.0625 })); assert_eq!(parse_complex(",-0.0625"), None); } /// Given the row and column of a pixel in the output image, return the /// corresponding point on the complex plan. /// /// `bounds` is a pair giving the width and height of the image in pixels. /// `pixel` is a (column, row) pair indicating a particular pixel in that image. /// The `upper_left` and `lower_right` parameters are pionts on the complex /// plain designating the area our image covers 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, // Why subtraction here? pixel.1 increases as we go down, // but the imaginary component increases as we go up. } } #[test] fn test_pixel_to_point() { assert_eq!( pixel_to_point( (100, 100), (25, 75), Complex { re: -1.0, im: 1.0 }, Complex { re: 1.0, im: -1.0 } ), Complex { re: -0.5, im: -0.5 } ); } /// Render a rectangle of the Mandlebrot set into a buffer of pixels. /// /// The `bounds` argument gives the width and height of the buffer `pixels`, /// which holds one grayscale pixel per byte. The `upper_left` and `lower_right` /// arguments specify points on the complex plane corresponding to the upper- /// left and lower-right corners of the pixel 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( 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, }; } } } /// Write the buffer `pixels`, whose dimensions are given by `bounds`, to 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 = std::env::args().collect(); if args.len() != 5 { writeln!( std::io::stderr(), "Usage: mandlebrot FILE PIXELS UPPERLEFT LOWERRIGHT" ).unwrap(); writeln!( std::io::stderr(), "Example: {} mandle.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]; // render(&mut pixels, bounds, upper_left, lower_right); 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); }); } }); } write_image(&args[1], &pixels, bounds) .expect("error writing PNG file"); }