extern crate crossbeam;
extern crate image;
extern crate num;
use num::Complex;
use std::fs::File;
use std::str::FromStr;
use image::codecs::png::PngEncoder;
use image::{ExtendedColorType, ImageEncoder};
use cached::proc_macro::cached;
use cached::SizedCache;
pub fn parse_complex(s: &str) -> Option<Complex<f64>> {
parse_pair(s, ',').map(|(re, im)| Complex { re, im })
}
pub fn parse_pair<T: FromStr>(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,
},
}
}
pub fn escape_time(c: Complex<f64>, limit: u32) -> Option<u32> {
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
}
pub fn pixel_to_point(
bounds: (usize, usize),
pixel: (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, }
}
pub 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(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,
};
}
}
}
pub fn render_parallel(
pixels: &mut [u8],
bounds: (usize, usize),
upper_left: Complex<f64>,
lower_right: Complex<f64>,
) {
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();
let _ = 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);
});
}
});
}
pub fn write_image(
filename: &str,
pixels: &[u8],
bounds: (usize, usize),
) -> Result<(), anyhow::Error> {
let output = File::create(filename)?; let encoder = PngEncoder::new(output);
encoder
.write_image(
pixels,
bounds.0 as u32,
bounds.1 as u32,
ExtendedColorType::L8,
)
.map_err(|e| anyhow::anyhow!(e))?;
Ok(())
}
#[cached(
ty = "SizedCache<String, bool>",
create = "{ SizedCache::with_size(100) }",
convert = r#"{ format!("{}_{}_{}_{}", args[0], args[1], args[2], args[3]) }"#
)]
pub fn write1(args: &[String]) -> bool {
let filename: &str = &args[0];
if args.len() != 4 {
eprintln!("Usage: mandelbrot FILE PIXELS UPPERLEFT LOWERRIGHT");
eprintln!("Example: Mandelbrot mandel.png 1000x750 -1.20,0.35 -1,0.20");
std::process::exit(1);
}
let bounds: (usize, usize) =
parse_pair(&args[1], 'x').expect("error parseing image dimensions");
let upper_left: Complex<f64> =
parse_complex(&args[2]).expect("error parseing upper left corner point");
let lower_right: Complex<f64> =
parse_complex(&args[3]).expect("error parseing lower right corner point");
let mut pixels = vec![0; bounds.0 * bounds.1];
render_parallel(&mut pixels, bounds, upper_left, lower_right);
write_image(filename, &pixels, bounds).expect("error writing PNG file.");
true
}