The Mandelbrot set in an html5 canvas

The Mandelbrot set is one of the pieces of mathematics behind the iconic fractal images of the 80s and 90s, and here I present how to render it on an html5 canvas, entirely in the browser.

The full mathematics of the Mandelbrot set are beyond the scope of this post, but can be found here. For our purposes, all we need to know is that we are dealing with a set of numbers – that is, every possible number either belongs to, or does not belong to, the set.

The Mandelbrot set deals with complex numbers – that is, numbers which have both a real and imaginary component. When we plot a fractal 2 dimensional image, each point represents a number, with a real component plotted in the horizontal axis, and the imaginary component plotted in the vertical axis (both purely by convention).

So, to render the image, we need to take every point within the image, determine which complex number it represents, determine whether or not that complex number belongs to the set, and colour it accordingly.

The definition of the Mandelbrot set includes the concept of iteration – performing the same function to that number over and over – and seeing whether or not the chain of numbers that results is bounded or not. In some cases, that is immediately obvious from the first iteration. In other cases closer to the edge of the set, it may take many more iterations to determine.

One characteristic feature of the Mandelbrot set (and fractals by definition) is that it is nowhere differentiable – on inspection, more detail appears, and a straight line can never be found. This means that the only way to determine if a point lies within the set is to calculate for that specific point, and the closer that point lies to the boundary of the set, the more iterations are required. So, in order to avoid having to compute potentially infinite iterations for points that lie on the edge, we instead assume that each number is in the set, and if after a set number of iterations we have not proved a number is outside the set, we stick with that assumption.

First we define an html5 canvas:

1
2
3
4
<html>
<head><title>Mandelbrot Canvas</title></head>
<body>
<canvas id="mandelbrot" width="1000" height="700"></canvas>

We then set up some initial parameters.

We are going to limit each calculation to 30 iterations, as this gives a good level of detail at the screen resolution we are dealing with:

5
6
<script type="text/javascript">
var MaxIterations = 30;

The initial set of complex numbers we are going to use runs from -2+1j at the top left of the image down to 1-1j at the bottom right. This encloses the Mandelbrot set nicely, giving us the stereotypical view of the set. As we are going to keep the aspect ratio fixed (i.e. 1 unit in the x axis will always equal 1 unit in the y axis) the 4th limit is calculated from the other 3:

7
8
9
10
var MinRe = -2.0;
var MaxRe = 1.0;
var MinIm = -1.0;
var MaxIm = MinIm+(MaxRe-MinRe)*ImageHeight/ImageWidth;

It is important to note that we are dealing with 2 sets of co-ordinates: the 2 dimensional matrix of complex numbers defined above, as well as the matrix of pixels in the canvas. We retrieve the height and width of the canvas element and calculate (in units per pixel) the scaling factors between the two sets of co-ordinates:

11
12
13
14
15
var element = document.getElementById("mandelbrot");
var ImageHeight=element.height;
var ImageWidth=element.width;
var Re_factor = (MaxRe-MinRe)/(ImageWidth-1);
var Im_factor = (MaxIm-MinIm)/(ImageHeight-1);

The canvas element’s pixel data is stored in a one-dimensional array of rgb and alpha values. As we will be manipulating the canvas at a pixel level, it will be simpler to define a function which amends the array for a given pixel position:

16
17
18
19
20
21
22
23
//sets pixel at co-ordinate x,y to rgb and alpha values given
function setPixel(imageData, x, y, r, g, b, a) {
    index = (x + y * imageData.width) * 4;
    imageData.data[index+0] = r;
    imageData.data[index+1] = g;
    imageData.data[index+2] = b;
    imageData.data[index+3] = a;
}

Next we create a function which performs the iterative calculation for each pixel, and colours it either black or white depending on whether it belongs to the set or not:

24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
function reDraw(){
    c1 = element.getContext("2d");
    c1Data = c1.getImageData(0, 0, ImageWidth, ImageHeight);

    for(var y=0; y<ImageHeight; y++)
    {
        var c_im = MaxIm - y*Im_factor;
        for(var x=0; x<ImageWidth; x++)
        {
            var c_re = MinRe + x*Re_factor;
            var Z_re = c_re, Z_im = c_im;
            var isInside = true;
            mainloop:
            for(var n=0; n<MaxIterations; n++)             {                 var Z_re2 = Z_re*Z_re, Z_im2 = Z_im*Z_im;                 if(Z_re2 + Z_im2 > 4)
                {
                    isInside = false;
                    break mainloop;
                }
                Z_im = 2*Z_re*Z_im + c_im;
                Z_re = Z_re2 - Z_im2 + c_re;
            }
            if(isInside){
                r=0;
                g=0;
                b=0;
            }
            else{
                r=255;
                g=255;
                b=255;
            }
            setPixel(c1Data, x, y, r, g, b, 0xff);
        }
    }
    c1.putImageData(c1Data, 0, 0);
}

We then call the function to draw the set according to our initial parameters:

63
reDraw();

This gives us a basic black and white image according to set membership:

While nice enough, it’s missing the well-known colouration we expect from our fractals. So we instead define the colour of each number not only according to set membership, but also by the number of iterations used to determine membership, and whether the maximum nummber of iterations were reached without determination. Lines 48 to 57 above now become:

48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
if (n<MaxIterations/2-1){
    r=(n/(MaxIterations/2-1))*255;
    g=0;
    b=0;
}
else if (n<MaxIterations-1){
    r=255;
    g=((n-(MaxIterations/2))/(MaxIterations/2))*255;
    b=((n-(MaxIterations/2))/(MaxIterations/2))*255;
}
else if(isInside){
    r=0;
    g=0;
    b=0;
}
else{
    r=255;
    g=255;
    b=255;
}

A full working example of this can be seen here, also incorporating a basic zoom function using jquery.

8 Comments

  1. Otter Fan says:

    I’m disappointed by the lack of otters in this post.

  2. graeme says:

    They’re in there, if you zoom in far enough.

  3. Adrian says:

    Thank you. This helped me a lot. Very good Explanation!

    • Johnie says:

      But you didn’t answer the question. When a government takes it upon itself to decide the fate of their citizens through “special pr#;samr&o8221g, tell me what makes it right or wrong? God? Zeus? The Orange Blossom Special?

  4. Roma says:

    The Mandelbrot set is a standout amongst the most unpredictable and lovely numerical articles known. Utilizing HTML5 Canvas, File API, touch (Pointer and motion occasions), and Web Workers.
    https://sovinco.com/

Leave a Reply

Your email address will not be published. Required fields are marked *

*

question razz sad evil exclaim smile redface biggrin surprised eek confused cool lol mad twisted rolleyes wink idea arrow neutral cry mrgreen

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>