You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
165 lines
5.7 KiB
Rust
165 lines
5.7 KiB
Rust
#![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 <left><sep><right>, where <sep> is
|
|
/// the character given by the `separator` argument, and <left> and <right> 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<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
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_parse_pair() {
|
|
assert_eq!(parse_pair::<i32>("", ','), None);
|
|
assert_eq!(parse_pair::<i32>("10,", ','), None);
|
|
assert_eq!(parse_pair::<i32>(",10", ','), None);
|
|
assert_eq!(parse_pair::<i32>("10,20", ','), Some((10, 20)));
|
|
assert_eq!(parse_pair::<i32>("10,20xy", ','), None);
|
|
assert_eq!(parse_pair::<f64>("0.5x", 'x'), None);
|
|
assert_eq!(parse_pair::<f64>("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<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);
|
|
}
|
|
}
|
|
|
|
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<String> = 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");
|
|
}
|