Blistering fast Mandelbrot rendering in Rust

submited by
Style Pass
2024-06-06 15:00:08

The Mandelbrot set is the set of complex numbers \(c\) for which the function \(f_c(z)=z^2+c\) does not escape to infinity when iterated starting from \(z=0\). In the classic multicolored rendering that everyone is familiar with each color represents how quickly the iteration diverged.

In this article I’ll show how we can render the image above1 in Rust using increasingly complex and faster approaches.The algorithm in pseudocode

Obviously Wikipedia as an article on plotting algorithms for the Mandelbrot set. The basic algorithm is very straightforward:for each pixel (Px, Py) on the screen: x0 = scaled x coordinate to lie between -2.5 and 1 y0 = scaled y coordinate to lie between -1 and 1 x = 0.0 y = 0.0 iteration = 0 max_iteration = 1000 while (x*x + y*y <= 4 and iteration < max_iteration): xtemp = x*x - y*y + x0 y = 2*x*y + y0 x = xtemp itearation = iteration + 1 color = palette[iteration] put color at coordinates Px, Py Naive implementation

The naive implementation is a straight up porting of the code above:use image; use std::format; use std::io::{self, Write}; use std::path::Path; use std::time::Instant; // Function to map the number of iterations i to a grey value between 0 (black) // and 255 (white). fn get_color(i: u32, max_iterations: u32) -> image::Rgb<u8> { if i > max_iterations { return image::Rgb([255, 255, 255]); } if max_iterations == 255 { let idx = i as u8; return image::Rgb([idx, idx, idx]); } let idx = (((i as f32) / (max_iterations as f32)) * 255.0).round() as u8; return image::Rgb([idx, idx, idx]); } // Function to run a Mandelbrot rendering algorithm and measure its execution // time. // Arguments: // name: name of the algorithm, it's used to print its name and save the output. // w: width of the output image, in pixels. // h: height of the output image, in pixels. // save_image: if true, save the output of the algorithm to // /tmp/mandelbrot_{name}.png // algo: actual rendering algorithm that should take as inputs the width and // height of the output image and returns an image::RgbImage fn runalgo(name: &str, w: u32, h: u32, save_image: bool, algo: fn(u32, u32) -> image::RgbImage) { print!("Executing {}... ", name); io::stdout().flush().unwrap(); let now = Instant::now(); let img = algo(w, h); let elapsed = now.elapsed().as_millis() as f32 / 1000.0; if save_image { let fname = format!("mandelbrot_{}.png", name); img.save_with_format(Path::new("/tmp").join(fname), image::ImageFormat::Png) .unwrap(); } println!("{}s", elapsed); } // Render the Mandelbrot set. w and h are respectively the widht and height of // the output image. Refer to // https://en.wikipedia.org/wiki/Plotting_algorithms_for_the_Mandelbrot_set for // a detailed explanation of this algorithm. fn naive(w: u32, h: u32) -> image::RgbImage { let mut img = image::RgbImage::new(w, h); for c in 0..w { let x0 = ((c as f32) / (w as f32)) * 3.5 - 2.5; for r in 0..h { let y0 = ((r as f32) / (h as f32)) * 2.0 - 1.0; let mut x = 0.0; let mut y = 0.0; let mut iteration: u32 = 0; while x * x + y * y <= 4.0 && iteration < MAX_ITERATIONS { let xtemp = x * x - y * y + x0; y = 2.0 * x * y + y0; x = xtemp; iteration = iteration + 1; } let rgb = get_color(iteration, MAX_ITERATIONS); img.put_pixel(c, r, rgb); } } img } fn main() { let width = 14000; let height = 8000; let save_image = false; runalgo("naive", width, height, save_image, naive); }

Leave a Comment