Modular Fractal Viewer

How to Make a Fractal

    Here's a quick tutorial on how to make the Mandelbrot set. Let's take a complex number and call it c. Then we will add it to a complex number z that is initially zero. Then, we will square our z, and add c again and again. For example, if we start with c = 1 + 0i, our z will go through the values 0, 1, 2, 5, 26, .... As you can see, this number gets out of control very fast. What if we pick a number like c = 0 + 1i? Then our z looks like: 0, i, -1 + i, -i, 1 - i, -i, .... This value gets stuck in a cycle, and doesn't get very large. The numbers that grow very fast are colored based on how fast the sum of the magnitude of their real and imaginary components reach the value 16. For example, our c = 1 + 0i became greater than 16 in five iterations, so we will color it along with all the other points that reached 16 in five iterations an arbitrary color. The points that iterate forever and never reach 16, we color black, as they are part of the mandelbrot set. Let a computer do this for all the pixels on screen, and you get an image that looks like this:

The Mandelbrot Set

    Read until the end and you'll get to see some cool animations where I modify our definition of the Mandelbrot to make some cool visuals.

    But how would we put this idea into code? For some reason, I thought that doing operations on complex numbers was going to be difficult because computers don't normally deal with them, but then after I started playing around, I quickly learned that complex numbers are really just two real numbers, but one of the numbers has some funky properties, and the complex number as a whole can have some funky things done to it.

    I first started coding this fractal viewer in Java, as a product of my boredom in my Computer Science class. You can read about it in this article. I later ported it to C, because I am much more comfortable with optimizing it there. So let's start the journey from when I ported it to C.

Defining Imaginary Numbers

    The start of my journey began with a simple struct:

/* A complex number.  */
typedef struct
{
    double aReal;
    double aImag;
}complex_t;

    With this, I could start writing some functions to do operations on complex numbers, such as squaring and adding.

/* Squares a complex number.  */
void sqr_complex( complex_t *spVal )
{
    /* Binomial expansion.  */
    double real  = ( spVal->aReal * spVal->aReal ) - ( spVal->aImag * spVal ->aImag );
    spVal->aImag = 2 * spVal->aReal * spVal->aImag;
    spVal->aReal = real;
}
/* Adds two complex numbers.  */
void add_complex( complex_t *spVal, complex_t sAdd )
{
    spVal->aReal += sAdd.aReal;
    spVal->aImag += sAdd.aImag;
}

A Quick Image Format

    I quickly wrote an image struct that is basically just a pointer to the pixel data, and dimensions. I also wrote some functions to create the images and free them.

The Render/Calculations

    Doing the calculations and rendering it to the screen was pretty straight forward. I just made a function named calc_mandel that takes in some transformation arguments and an image. You can supply a real number offset, and an imaginary number offset, as well as a zoom level. This way, I can zoom into my Mandelbrot to see all its complex (haha) beauty. I do some math to assign each pixel a complex c value. I divide the width and height by two, add the offset, add the index of pixel, and divide the whole thing by the zoom. Then, for each pixel, I made a loop that performs the squaring and adding, for an arbitrary amount of iterations. Finally, based on the number of iterations required to reach the magnitude of 16, I color the pixel.

    I can't actually remember how exactly I did it at first because since then, I multithreaded it to squeeze more performance out of the program. I have twelve threads on my CPU, so I will use all of them. I did some math to split the coordinates into twelve horizontal pieces, and then ran a function that did the calculations for a select region of the zoom in a pthread to render the fractal twelve times faster. It almost worked. For some reason the very first thread has trouble getting the correct region, and often renders a region that is assigned to another thread. I have no idea why this happens, but when it's apparent when I am zooming into the fractal, and there is a band on the top of the render that appears frozen, and only updates occasionally.

/* Iterates a complex number.  */
pixel_t iterate_mandel( complex_t c )
{
    u32       iterations = 0;
    complex_t z = { 0.f, 0.f };
    for ( u32 i = 0; i < gMaxIterations; ++i )
    {
        pow_complex( &z, gBrotDimension );
        if ( mag_complex( z ) > 16 )
            break;

        ++iterations;
        add_complex( &z, c );
    }
    /* Is part of the set.  */
    if ( iterations == gMaxIterations )
            return 0x000000;
    /* Give it a cool grey color if there is just nothing to show.  */
    else if ( iterations == 1 )
            return 0x111111;
        else
            return gpColors[ iterations % gColorCount ];
}
/* Threaded calculate function.  */
void *threaded_calc_mandel( void *spArgs )
{
    /* I don't know why this is named test_t, lol.  */
    test_t args = *( test_t* )spArgs;
    for ( int y = args.y0; y < args.yf; ++y )
    {
        for ( int x = 0; x < args.apImage->aWidth; ++x )
        {
            complex_t c = {
                ( -args.apImage->aWidth  / 2.f + x + args.aRealOff * args.aZoom ) / args.aZoom,
                ( -args.apImage->aHeight / 2.f + y + args.aImagOff * args.aZoom ) / args.aZoom
            };
                args.apImage->apData[ y * args.apImage->aWidth + x ] = iterate_mandel( c );
        }
    }
}

    You may be wondering what the pow_complex function does. After playing with the simple squaring function, I wanted to improve it to be able to operate on any real exponent, so I can see what a Mandelbrot with the equation z = z^2.1 + c looks like. Currently, it doesn't work for odd exponents, and I wish I knew why, but it works well for anything else. Later, I will add more operations on the complex numbers such as trigonometric ones, so I can do some other weird fractals like z = sin(z^2) + c

Here's a Reward for Reading This Far

    Normally, when you look up visualizations of the Mandelbrot set, you see zooms into the set. However with my program, you can make some other animations, such as changing the exponent on the z in the Mandelbrot equation, or changing the threshold for a point to be in the mandelbrot set. Here's a video showing some of these animations: