The Mandelbrot set in C++11
Posted on February 28, 2013 by Paul
The code for this post is on GitHub: https://github.com/sol-prog/Mandelbrot_Set.
Yet another how to draw the Mandelbrot set article ? Well, yes and no, on the one hand fractals are fun and on the other hand, it could be instructive to play with complex numbers and lambdas in C++11. Also, the article presents a not so common continuous coloring procedure based on a slight modification of the Bernstein polynomials.
Please note, that calculating the Mandelbrot set can be done more efficiently if one uses the GPU (using OpenGL shaders for example) and not the CPU.
Let’s start by creating a small class that will let us define the domain in which we search for points from the Mandelbrot set:
we could also use the above class to store the bounds of an image, or the bounds of a window on our computer screen.
The Mandelbrot set is defined by the complex polynomial:
$$ z \mapsto {z^2} + c $$
where \(c \in \mathbb{C}\) is a parameter.
We can implement this in C++11 as a lambda:
that could be easily passed, as a parameter, to other functions.
In order to obtain a nice picture, we are going to map the continuous domain in which we search for points from the Mandelbrot set to a discrete domain that represents pixel coordinates:
For simplicity, we will use the escape time algorithm. Basically, for every pixel, we iterate over the formula \({z_{n + 1}} = {z_n}^2 + c\) until \(\mid {z_n} \mid \gt 2\) or a maximum number of iterations is reached. We’ll use the iteration numbers to assign a particular color to every pixel. These are the functions called by mandelbrot():
The question that remains to be solved is how to assign colors to a point from the Mandelbrot set. My first attempt was to use a linear map between the number of iterations and the number of possible values for an RGB image. We start by dividing the number of iterations corresponding to a particular pixel to the maximum number of iterations, this will gives us a number t on the unit interval. An RGB image can have \(256^3\) colors, so we multiply t with \(256^3\), cast the value to an integer that is used as an index in an array of colors. Obviously, we are not going to generate and store \(256^3\) colors, that will be silly. If we imagine our virtual array of colors as a 3D array, we can get the r, g, b indices with the following function:
An alternative to the above approach, with similar results, will be to cast the value of t multiplied with \(256^3\) to an unsigned 32 bits integer (4 bytes). Since the r, g, b numbers are 1 byte each, we can read their values from the 32 bits unsigned integer previously calculated.
The resulting image was saved on the disk with the FreeImage library, you can see the complete code, save_image.cpp, on the Github repository for this article.
The map between the iteration number and the 3D color space was made using three piecewise linear functions, the discontinuous nature of this map will be reflected in the resulting image band effect:
If we zoom close enough to one of the bulbs of the set, we’ll have an even nicer image, unfortunately still banded:
There are various tricks with which we can obtain a more continuous image, one of them, suggested in the cited Wikipedia article is the Normalized Iteration Count algorithm. The problem with this algorithm is that it needs modifications for different, but related, fractals like the Julia set.
The key to a nice image, without color bands, is the word continuous! I mean, how do we expect to obtain a smooth transition from color to color if we use a discontinuous map between the iteration count and the 3D color space ?
Well, the solution is not that complicated - use three smooth, continuous functions that will map every number t. Remember that t was obtained by dividing the number of iterations for every point in the set with the maximum number of iterations, on the unit interval. We could use any smooth polynomial that is defined on the unit interval and has values on the same interval. A slightly modified form of the Bernstein polynomials looks like the perfect match for what we need, they are continuous, smooth and have values in the [0, 1) interval:
\[r(t) = 9 \cdot \left( {1 - t} \right) \cdot {t^3}\] \[g(t) = 15 \cdot {\left( {1 - t} \right)^2} \cdot {t^2}\] \[b(t) = 8.5 \cdot {\left( {1 - t} \right)^3} \cdot t\]We can plot the above functions in order to see how smooth they are:
Implementing the above functions in C++ is straightforward:
Let’s see now, the effect of using a smooth map from the iteration numbers to the 3D RGB space:
If we zoom again close enough to one of the bulbs of the set, we’ll have an even nicer image, this time without any band effect:
Suppose now that you’ve had enough fun with the classical Mandelbrot set and you want to try something new, like the third order Mandelbrot set :). How can you add this to the above code ? We’ll need to define a new driver function, say triple_mandelbroot:
Let’s see how it looks:
after a zoom:
If you are interested in learning more about the C++11 Standard Library, I would recommend reading The C++ Standard Library: A Tutorial and Reference (2nd Edition) by N. M. Josuttis:
or Professional C++ by M. Gregoire, N. A. Solter, S. J. Kleper 2nd edition: