commit c6ebcfea12f386542fd3fa7c668df406cf621b14 Author: Jim Blandy Date: Tue Oct 13 21:29:33 2015 -0700 working single-threaded diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..411f97b --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +*.png +target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..d2a586e --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,224 @@ +[root] +name = "mandelbrot" +version = "0.2.0" +dependencies = [ + "image 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)", + "num 0.1.34 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "bitflags" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "byteorder" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "color_quant" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "deque" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "enum_primitive" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "num 0.1.34 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "flate2" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.15 (registry+https://github.com/rust-lang/crates.io-index)", + "miniz-sys 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "gcc" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "gif" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "color_quant 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "lzw 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "glob" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "image" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "byteorder 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)", + "enum_primitive 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "gif 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", + "glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", + "jpeg-decoder 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", + "num-iter 0.1.32 (registry+https://github.com/rust-lang/crates.io-index)", + "num-rational 0.1.32 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits 0.1.34 (registry+https://github.com/rust-lang/crates.io-index)", + "png 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "inflate" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "jpeg-decoder" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "byteorder 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)", + "rayon 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "libc" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "lzw" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "miniz-sys" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "gcc 0.3.32 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.15 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "num" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "num-bigint 0.1.33 (registry+https://github.com/rust-lang/crates.io-index)", + "num-complex 0.1.33 (registry+https://github.com/rust-lang/crates.io-index)", + "num-integer 0.1.32 (registry+https://github.com/rust-lang/crates.io-index)", + "num-iter 0.1.32 (registry+https://github.com/rust-lang/crates.io-index)", + "num-rational 0.1.32 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits 0.1.34 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "num-bigint" +version = "0.1.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "num-integer 0.1.32 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits 0.1.34 (registry+https://github.com/rust-lang/crates.io-index)", + "rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", + "rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "num-complex" +version = "0.1.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "num-traits 0.1.34 (registry+https://github.com/rust-lang/crates.io-index)", + "rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "num-integer" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "num-traits 0.1.34 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "num-iter" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "num-integer 0.1.32 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits 0.1.34 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "num-rational" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "num-bigint 0.1.33 (registry+https://github.com/rust-lang/crates.io-index)", + "num-integer 0.1.32 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits 0.1.34 (registry+https://github.com/rust-lang/crates.io-index)", + "rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "num-traits" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "num_cpus" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.15 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "png" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", + "flate2 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)", + "inflate 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "num-iter 0.1.32 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rand" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.15 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rayon" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "deque 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "num_cpus 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)", + "rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rustc-serialize" +version = "0.3.19" +source = "registry+https://github.com/rust-lang/crates.io-index" + diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..2631867 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "mandelbrot" +version = "0.2.0" +authors = ["Jim Blandy "] + +[dependencies] +num = "0.1.34" +image = "0.10.1" diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..f494e7f --- /dev/null +++ b/src/main.rs @@ -0,0 +1,164 @@ +#![allow(dead_code, unused_variables)] + +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", ','), 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))); +} + +/// Return the point on the complex plane corresponding to a given pixel in the +/// bitmap. +/// +/// `bounds` is a pair giving the width and height of the bitmap. `pixel` is a +/// pair indicating a particular pixel in that bitmap. The `upper_left` and +/// `lower_right` parameters are points on the complex plane designating the +/// area our bitmap covers. +fn pixel_to_point(bounds: (usize, usize), + pixel: (usize, usize), + upper_left: (f64, f64), + lower_right: (f64, f64)) + -> (f64, f64) +{ + // It might be nicer to find the position of the *middle* of the pixel, + // instead of its upper left corner, but this is easier to write tests for. + let (width, height) = (lower_right.0 - upper_left.0, + upper_left.1 - lower_right.1); + (upper_left.0 + pixel.0 as f64 * width / bounds.0 as f64, + upper_left.1 - pixel.1 as f64 * height / bounds.1 as f64) +} + +#[test] +fn test_pixel_to_point() { + assert_eq!(pixel_to_point((100, 100), (25, 75), + (-1.0, 1.0), (1.0, -1.0)), + (-0.5, -0.5)); +} + +extern crate num; +use num::Complex; + +/// Try to determine whether the complex number `c` is in the Mandelbrot set. +/// +/// A number `c` is in the set if, starting with zero, repeatedly squaring and +/// adding `c` never causes the number to leave the circle of radius 2 centered +/// on the origin; the number instead orbits near the origin forever. (If the +/// number does leave the circle, it eventually flies away to infinity.) +/// +/// If after `limit` iterations our number has still not left the circle, return +/// `None`; this is as close as we come to knowing that `c` is in the set. +/// +/// If the number does leave the circle before we give up, return `Some(i)`, where +/// `i` is the number of iterations it took. +fn escapes(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); + } + } + + return None; +} + +/// Render a rectangle of the Mandelbrot 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: (f64, f64), lower_right: (f64, f64)) +{ + assert!(pixels.len() == bounds.0 * bounds.1); + + for r in 0 .. bounds.1 { + for c in 0 .. bounds.0 { + let point = pixel_to_point(bounds, (c, r), + upper_left, lower_right); + pixels[r * bounds.0 + c] = + match escapes(Complex { re: point.0, im: point.1 }, 255) { + None => 0, + Some(count) => 255 - count as u8 + }; + } + } +} + +extern crate image; + +use std::fs::File; +use std::io::Result; +use image::png::PNGEncoder; +use image::ColorType; + +/// Write the buffer `pixels`, whose dimensions are given by `bounds`, to the +/// file named `filename`. +fn write_bitmap(filename: &str, pixels: &[u8], bounds: (usize, usize)) + -> Result<()> +{ + let output = try!(File::create(filename)); + + let encoder = PNGEncoder::new(output); + try!(encoder.encode(&pixels[..], + bounds.0 as u32, bounds.1 as u32, + ColorType::Gray(8))); + + Ok(()) +} + +use std::io::Write; + +fn main() { + let args : Vec = std::env::args().collect(); + + if args.len() != 5 { + writeln!(std::io::stderr(), + "Usage: mandelbrot FILE PIXELS UPPERLEFT LOWERRIGHT") + .unwrap(); + writeln!(std::io::stderr(), + "Example: {} mandel.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_pair(&args[3], ',') + .expect("error parsing upper left corner point"); + let lower_right = parse_pair(&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); + + write_bitmap(&args[1], &pixels[..], bounds).expect("error writing PNG file"); +}