Last week, I started knuckling down and praticing Rust for seriousness. I've been kinda skating along the top, not learning it in any real way; I'd been doing that for a while at my last job, where they insisted I use Go instead. I'm not fond of Go; I think it's an amazingly powerful idiomatic garbage collection and inter-thread communications framework on top of which is build the godawfulest ugly language I've ever seen.
Last week I figured out how to render the Mandelbrot set. It's the last exercise in chapter two of Programming Rust, and after I was done writing it, I decided to see if I could make a few improvements.
So here's the basic thing about the Mandelbrot set: it's a little arbitrary. The Mandelbrot set lives in the ℂ plane in a subplane ℤ described by the points 2+2_i_ to -2-2_i_; the objective is to pick a subregion of that square, and map it to an arbitrary pixel plane, say an image 1024x768. For each pixel in that pixel plane:
* figure out what point `c` on the ℂ plane most closely maps to that pixel.
* Start with a complex number `z` representing the origin: 0+0_i_.
* iterate on `z = z2+c`
* if `z` does not leave the subplane ℤ, leave the region black
* if `z` does leave the subplane, the iteration number when it violated the subplane border can be used to map to a color
Note that the base Mandelbrot algorithm returns only a single digit, and is appropriate only for greyscale images. The typically colorful Mandelbrot images you see are mappings of arbitrary colormamps to the grayscale values, much the same way GIFs are colored. There are other algorithms for choosing coloring, and I haven't looked much into them.
The source code for my efforts on my Github under programming_rust/mandelbrot. There are two changes to the example in the book:
1. I realized early on that every function was receiving the same two variables: a tuple describing the pixel plane, a tuple describing the complex plane. These values never change during the operation of a single render, and since I have programmed a bit of Rust over the past year or so I realized that these could best be put into a struct and have implementations range over them, so now we have an object `Plane`. This makes the function `pixel_to_point()` something that _just_ takes a pixel and returns a complex number. It also means that every iteration through `render()` no longer has to recalculate the mapping every time; it's done once and done for good.
2. I decided to make my output into PNM. I'm a big fan of the Portable Anymap format. I know it's not most people's favorite, but it works for me and I know how to process it well.
I also did the concurrent version of the algorithm. This involves slicing up the plane into subplanes; points on the Mandelbrot set are independent of their neighbors (unlike, say, a Conway Game of Life), so making it concurrent is simply a map of slicing the plane into uniform subplanes based on how many cores your CPU can support, and giving each core a plane to process.
At first, I worried about how this was going to work with my Plane
object, but then I realized that the Plane
itself had the capacity to provide its own slices, and the method subplane()
does exactly that. Then it's simply a matter of mapping the subplane to a band of pixels in the image buffer, creating a thread having the thread call render()
on the subplane.
I believe this code is easier to read than what's in the book. Yes, it relies on structs, which aren't introduced for quite a few chapters, so it's cheating, but it's fun.
There are two things I'd like to do with this: one is colorize it. My problem so far is that I don't know how to read Rust type signatures, so figuring out how, using Rust's most popular image processing library, to colorize an image has failed me so far.
On the other hand, generating a basic Buddhabrot turned out to be fairly easy. That's what you're seeing in that illustration above.
A Buddhabrot says that for iterating function z = z2 + c
, each resulting z
represents another point inside the plane ℤ. Instead of counting when an iteration point leaves the plane ℤ, a Buddhabrot simply increments every pixel z
represents during a set number of iterations. The resulting images are ghostly and strange, and looked to be a lot of fun. So I did it, and it worked, and I'm happy with the result