4
\$\begingroup\$

I'm in the process of familiarizing myself with Rust. In order to get some practice, I decided to make a program that generates images of the Mandelbrot set.

The function I use to generate the image is included below. I'm specifically interested in feedback regarding my use (or lack thereof) of Rust idioms, as well as my interaction with the borrowing system. Notice that I had to clone the PathBuf as I have two uses of it, and the call to save borrows the value. Of course, however, any and all feedback is appreciated as well.

use image::{ImageBuffer, Rgb};
use num::Complex;
use std::path::PathBuf;


pub fn generate_image(width: u32, height: u32, iterations: u32, zoom: f32, out: PathBuf) {
    let to_imaginary_domain = |x: u32, y: u32| -> (f32, f32) {
        let re: f32 = x as f32 - width as f32 / 2.0;
        let im: f32 = y as f32 - height as f32 / 2.0;
        (re / zoom, im / zoom)
    };
    
    println!("Generating {} x {} image of the Mandelbrot set...", width, height);
    
    let img = ImageBuffer::from_fn(width, height, |px, py| {
        let (x, y) = to_imaginary_domain(px, py);
        let c = Complex::<f32> { re: x, im: y };
        let mut z = Complex::<f32> { re: 0.0, im: 0.0 };
        for _i in 0..iterations {
            z = z * z + c;
            if z.norm() >= 2.0 {
                return Rgb::<u8>([0x00, 0x00, 0x00]);
            }
        }
        Rgb::<u8>([0xFF, 0xFF, 0xFF])
    });
    
    match img.save(out.clone()) {
        Ok(_) => {
            println!("Successfully saved image to {:#?}.", out.as_os_str());
        },
        Err(error) => {
            panic!("Failed to save the image: {:#?}", error);
        }
    };
}
\$\endgroup\$

1 Answer 1

3
\$\begingroup\$

Disclaimer: I'm not an experienced Rust developer.

Note for other reviewers/people who want to test the code

Here are the dependencies you can use:

[dependencies]
image = "0.24.4"
num = "0.4.0"

and here are parameters that worked fine for me:

  • width: 1000
  • height: 1000
  • iterations: 100
  • zoom: 300

Clippy

clippy is a really nice tool to catch mistakes and improve your code.

In your case, there is not much to say:

  • a few things about integer types (but you are a bit stuck with the ImageBuffer::from_fn types)
  • a few things about Path and PathBuffer which may answer your initial question.

I ended up with:

/// # Panics
///
/// Will panic if image is not saved
pub fn generate_image(width: u32, height: u32, iterations: u32, zoom: f32, out: &Path) {
...
    match img.save(out) {
        Ok(_) => {
            println!("Successfully saved image to {:#?}.", out.as_os_str());
        }
        Err(error) => {
            panic!("Failed to save the image: {:#?}", error);
        }
    };

Splitting the logic in functions

Having a to_imaginary_domain function is nice but it is a bit surprising to me that it does not return a complex number.

    let to_imaginary_domain = |x: u32, y: u32| -> Complex<f32> {
        let re: f32 = x as f32 - width as f32 / 2.0;
        let im: f32 = y as f32 - height as f32 / 2.0;
        Complex::<f32> {
            re: re / zoom,
            im: im / zoom,
        }
    };

The mathematical operation probably deserves to be in a dedicated function as well.

#[must_use]
pub fn mandelbrot_func_diverges(c: Complex<f32>, iterations: u32) -> bool {
    let mut z = Complex::<f32> { re: 0.0, im: 0.0 };
    for _i in 0..iterations {
        z = z * z + c;
        if z.norm() >= 2.0 {
            return true;
        }
    }
    false
}

Then, in the from_fn call, we just have:

    let img = ImageBuffer::from_fn(width, height, |px, py| {
        if mandelbrot_func_diverges(to_imaginary_domain(px, py), iterations) {
            Rgb::<u8>([0x00, 0x00, 0x00])
        } else {
            Rgb::<u8>([0xFF, 0xFF, 0xFF])
        }
    });

If it was for me, I'd also rewrite the generate_image so that it returns the ImageBuffer instead of dealing with saving it, but it makes things slightly more verbose.

#[must_use]
pub fn generate_image(
    width: u32,
    height: u32,
    iterations: u32,
    zoom: f32,
) -> ImageBuffer<Rgb<u8>, Vec<u8>> {

More ideas

In order to make the output somehow better:

  • you could center the image around a different point

  • you could get the number of iterations before divergence and use this number to get a gradient of color

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.