Implemented the Mandelbrot set from Chapter 2 of Programming Rust
This implementation features concurrency, a little object-oriented code, and some old-school PNM.
This commit is contained in:
parent
413dc5322a
commit
1471a14f1a
|
@ -0,0 +1,11 @@
|
|||
target
|
||||
*#
|
||||
.#*
|
||||
*~
|
||||
*.orig
|
||||
*.aux
|
||||
*.log
|
||||
*.out
|
||||
*.pdf
|
||||
*.bk
|
||||
*/Cargo.lock
|
|
@ -0,0 +1,18 @@
|
|||
# Rust Exercises
|
||||
|
||||
This repository contains a collection of exercises in Rust, mostly taken
|
||||
from the O'Reilly's
|
||||
[*Programming Rust*](http://shop.oreilly.com/product/0636920040385.do),
|
||||
augmented with stretch goals, extensions, and a hint of arrogance.
|
||||
|
||||
## Currently available:
|
||||
|
||||
* Mandlebrot
|
||||
|
||||
Renders a Mandlebrot set
|
||||
|
||||
## ToDo
|
||||
|
||||
* Colorized Mandelbrot
|
||||
* [Buddhabrot](https://en.wikipedia.org/wiki/Buddhabrot)
|
||||
* Colorized Buddhabrot
|
|
@ -0,0 +1,11 @@
|
|||
[package]
|
||||
name = "Demo"
|
||||
version = "0.1.0"
|
||||
authors = ["elf"]
|
||||
|
||||
[dependencies]
|
||||
failure = "0.1.1"
|
||||
failure_derive = "0.1.1"
|
||||
num = '0.2.0'
|
||||
image = '0.19.0'
|
||||
crossbeam = '0.2.8'
|
|
@ -0,0 +1,19 @@
|
|||
# Mandelbrot
|
||||
|
||||
An implementation of the the Mandelbrot set exercise from the end of
|
||||
chapter two of O'Reilly's
|
||||
[*Programming Rust*](http://shop.oreilly.com/product/0636920040385.do).
|
||||
|
||||
I have deviated from the original in two ways, one major, one minor.
|
||||
|
||||
First, I've made the rendering plane, which maps from the cartesian
|
||||
(pixel) plane to the complex plane, into a struct, and put all the major
|
||||
rendering features into an implementation on that struct. By adding a
|
||||
simple method, `subplane()`, I was able to make crossbeam rendering much
|
||||
simpler and easier to read by providing horizontal slices of the pixel
|
||||
plane, and I was also able to avoid recalculating the relationship
|
||||
between the two planes for every pixel. A small performance boost, but
|
||||
worthwhile.
|
||||
|
||||
Second, the output is PNM rather than PNG. I'm a huge PNM partisan, and
|
||||
you kids better get off my lawn.
|
Binary file not shown.
|
@ -0,0 +1,206 @@
|
|||
extern crate num;
|
||||
extern crate image;
|
||||
extern crate crossbeam;
|
||||
|
||||
use num::Complex;
|
||||
use std::str::FromStr;
|
||||
use image::ColorType;
|
||||
use image::pnm::PNMEncoder;
|
||||
use image::pnm::{PNMSubtype, SampleEncoding};
|
||||
use std::io::Write;
|
||||
use std::fs::File;
|
||||
|
||||
|
||||
/// Try to determine if a complex number is a member of 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 2
|
||||
/// centered on the origin. If 'c' seems to be a member (if we
|
||||
/// reached the iteration limit without being able to prove 'c' is not
|
||||
/// a member), return 'None'
|
||||
///
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_complex(s: &str) -> Option<Complex<f64>> {
|
||||
match parse_pair(s, ',') {
|
||||
Some((re, im)) => Some(Complex{ re, im }),
|
||||
None => None
|
||||
}
|
||||
}
|
||||
|
||||
/// Contains the definitions of two planes: an integral cartesian plane,
|
||||
/// and a complex cartesian plane. Used to map points on one to the
|
||||
/// other.
|
||||
struct Plane {
|
||||
bounds: (usize, usize),
|
||||
upper_left: Complex<f64>,
|
||||
lower_right: Complex<f64>,
|
||||
width: f64,
|
||||
height: f64
|
||||
}
|
||||
|
||||
impl Plane where {
|
||||
|
||||
/// Define a mapping between the coordinates of an integral
|
||||
/// cartesian plane and a complex cartesian plane.
|
||||
pub fn new(width: usize, height: usize, ulp: Complex<f64>, lrp: Complex<f64>) -> Plane {
|
||||
let bound_width = width as f64;
|
||||
let bound_height = height as f64;
|
||||
Plane {
|
||||
bounds: (width, height),
|
||||
upper_left: ulp,
|
||||
lower_right: lrp,
|
||||
width: (lrp.re - ulp.re) / bound_width,
|
||||
height: (lrp.im - ulp.im) / bound_height,
|
||||
}
|
||||
}
|
||||
|
||||
/// Given the row and column of a pixel on the integral cartesian plane,
|
||||
/// return an complex number that corresponds to the equivalent location
|
||||
/// mapped to the complex cartesian plane.
|
||||
pub fn pixel_to_point(&self, pixel: (usize, usize)) -> Complex<f64> {
|
||||
Complex{
|
||||
re: self.upper_left.re + pixel.0 as f64 * self.width,
|
||||
im: self.upper_left.im + pixel.1 as f64 * self.height
|
||||
}
|
||||
}
|
||||
|
||||
pub fn render(&self, pixels: &mut[u8]) {
|
||||
assert!(pixels.len() == self.bounds.0 * self.bounds.1);
|
||||
for row in 0..self.bounds.1 {
|
||||
for column in 0..self.bounds.0 {
|
||||
let point = self.pixel_to_point((column, row));
|
||||
pixels[row * self.bounds.0 + column] =
|
||||
match escape_time(point, 255) {
|
||||
None => 0,
|
||||
Some(count) => 255 - count as u8
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn subplane(&self, top: usize, height: usize) -> Plane {
|
||||
let ulc = self.pixel_to_point((0, top));
|
||||
let lrc = self.pixel_to_point((self.bounds.0, top + height));
|
||||
Plane::new(self.bounds.0, height, ulc, lrc)
|
||||
}
|
||||
|
||||
pub fn render_concurrent(&self, pixels: &mut[u8], threads: usize) {
|
||||
let rows_per_band = self.bounds.1 / threads + 1;
|
||||
{
|
||||
let bands: Vec<&mut [u8]> = pixels.chunks_mut(rows_per_band * self.bounds.0).collect();
|
||||
crossbeam::scope(|spawner| {
|
||||
for (i, band) in bands.into_iter().enumerate() {
|
||||
let top = rows_per_band * i;
|
||||
let height = band.len() / self.bounds.0;
|
||||
let subplane = self.subplane(top, height);
|
||||
spawner.spawn(move || {
|
||||
subplane.render(band)
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn write_image(filename: &str, pixels: &[u8], bounds: (usize, usize)) -> Result<(), std::io::Error> {
|
||||
let output = File::create(filename)?;
|
||||
let mut encoder = PNMEncoder::new(output).with_subtype(PNMSubtype::Graymap(SampleEncoding::Binary));
|
||||
encoder.encode(pixels, bounds.0 as u32 ,bounds.1 as u32, ColorType::Gray(8))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
pub 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(), "Exaple: {} mandel.png 1000x750 -1.20,0.35 -1,02.0", 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 hand point");
|
||||
let lower_right = parse_complex(&args[4]).expect("Error parsing lower right hand point");
|
||||
|
||||
let mut pixels = vec![0; bounds.0 * bounds.1];
|
||||
let plane = Plane::new(bounds.0, bounds.1, upper_left, lower_right);
|
||||
|
||||
plane.render_concurrent(&mut pixels, 8);
|
||||
write_image(&args[1], &pixels, bounds).expect("Error writing PNM file");
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
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,
|
||||
lower_right.im - upper_left.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
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
#[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,20xy", ','), None);
|
||||
assert_eq!(parse_pair::<i32>("0.5x", ','), None);
|
||||
assert_eq!(parse_pair::<i32>("10,20", ','), Some((10, 20)));
|
||||
assert_eq!(parse_pair::<f64>("0.5x", ','), None);
|
||||
assert_eq!(parse_pair::<f64>("0.5x1.5", 'x'), Some((0.5, 1.5)));
|
||||
}
|
||||
|
||||
#[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);
|
||||
}
|
||||
|
||||
/*
|
||||
#[test]
|
||||
fn test_pixel_to_point() {
|
||||
assert_eq!(pixel_to_point((25, 75), (100, 100),
|
||||
Complex { re: -1.0, im: 1.0 },
|
||||
Complex { re: 1.0, im: -1.0 }), Complex { re: -0.5, im: -0.5 });
|
||||
}
|
||||
*/
|
||||
|
||||
#[test]
|
||||
fn test_plane() {
|
||||
let plane = Plane::new(100, 100, Complex{ re: -1.0, im: 1.0 }, Complex{ re: 1.0, im: -1.0 });
|
||||
assert_eq!(plane.pixel_to_point((25, 75)), Complex{ re: -0.5, im: -0.5 });
|
||||
}
|
||||
|
Loading…
Reference in New Issue