## Mandelbrot fractal in Rust

Mandelbrot fractal is a classic. Here is a WIP implementation in rust ; I’ve first ported mandelb0t’s version to python3 in order to run it, but it was terribly slow.

We’ll need to save images (`image`

crate) and to use complex numbers (`num-complex`

). Also we’ll put a sprinkle of parallelism for speed using `rayon`

.

```
$ cargo new mandelbrot --lib
$ cd mandelbrot
$ cargo add image num-complex rayon
Updating 'https://github.com/rust-lang/crates.io-index' index
Adding image v0.23.14 to dependencies
Adding num-complex v0.4.0 to dependencies
Adding rayon v1.5.0 to dependencies
```

We will use the classic architecture:

- a library as
`lib.rs`

- a binary that uses this lib in
`bin/mandelbrot.rs`

```
├── Cargo.lock
├── Cargo.toml
└── src
├── bin
│ └── mandelbrot.rs
└── lib.rs
```

One of the examples of the image crate is an implementation of the Julia fractal and we will use this as a base to save images.

In order to implement this fractal, we need to do 4 things:

- Compute the iteration time for a point in the complex plane
- Compute the iteration time for every pixel of an image
- Shade the pixels based on the number of iterations

That’s what our library will do:

```
# lib.rs
extern crate image;
extern crate num_complex;
use rayon::prelude::*;
fn compute_iterations_mandelbrot(complex_x: f32, complex_y: f32, max_iterations: usize) -> usize {
// Counts if the complex point c(cx, cy) diverges (it’s norm is > 2.0) in a finite
// amount of time (the max amount of iterations)
let c = num_complex::Complex::new(complex_x, complex_y);
let mut z = num_complex::Complex::new(0f32, 0f32);
let mut nb_iterations = 0;
while nb_iterations < max_iterations && z.norm() < 2.0 {
z = z * z + c;
nb_iterations += 1;
}
nb_iterations
}
pub fn compute_iterations(
width: u32,
height: u32,
xa: f32,
xb: f32,
ya: f32,
yb: f32,
max_iterations: usize,
) -> Vec<usize> {
(0..width * height)
.into_par_iter()
.map(|offset| {
// extract the x, y coordinates out of the linear offset
let image_x = offset % width;
let image_y = offset / width;
// convert the x, y pixel coordinates into values of the complex plane in the area [xa, xa], [ya, yb]
let complex_x = (image_x as f32) * (xb - xa) / (width as f32 - 1.0f32) + xa;
let complex_y = (image_y as f32) * (yb - ya) / (height as f32 - 1.0f32) + ya;
compute_iterations_mandelbrot(complex_x, complex_y, max_iterations)
})
.collect()
}
pub fn save_image(
nb_iterations: &[usize],
width: u32,
height: u32,
max_iterations: usize,
path: &str,
) {
let mut imgbuf = image::ImageBuffer::new(width, height);
for (x, y, pixel) in imgbuf.enumerate_pixels_mut() {
let i = nb_iterations[(y * width + x) as usize];
// Shade pixel based on the number of iterations somehow
let red: u8 =
(((max_iterations as f32 - i as f32) / (max_iterations as f32)) * 255f32) as u8;
let (green, blue) = (red, red);
*pixel = image::Rgb([red as u8, green as u8, blue as u8]);
}
imgbuf.save(path).unwrap();
}
```

For now, shading is minimalistic. We mostly care to display if a point is part of the Mandelbrot set or not using shades of black and white. The nuances based on the number of iterations may not be very visible.

Then, we can use our library in a sample binary:

```
# bin/mandelbrot.rs
extern crate mandelbrot;
use mandelbrot::{compute_iterations, save_image};
fn main() {
let width: u32 = 900;
let height: u32 = 900;
let xa: f32 = -2.0;
let xb: f32 = 1.0;
let ya: f32 = -1.5;
let yb: f32 = 1.5;
let max_iterations = 256;
let nb_iterations = compute_iterations(width, height, xa, xb, ya, yb, max_iterations);
save_image(
&nb_iterations,
width,
height,
max_iterations,
"mandelbrot.png",
);
}
```

Then, we can run our program, and it’s reasonnably fast:

```
$ cargo run --bin mandelbrot
$ cargo run --bin mandelbrot --release
$ cargo build --release && time ./target/release/mandelbrot
Finished release [optimized] target(s) in 0.01s
real 0m0,054s
user 0m0,438s
sys 0m0,000s
```

See a typo ? You can suggest a modification on Github.