Software | Web | Project Management

HTML5 Canvas Fractal

I always wanted to create a fractal generator since experimenting with fractal programs on my Amiga many years ago. Fractals are fascinating, beautiful patterns generated mathematically. They have infinite complexity, and you can explore uncharted depths by zooming on different areas. In this case I'm using the Mandelbrot set, but there are many others.

The <canvas> element introduced in HTML5 provides an easy means to draw onto a defined area, dispensing the need for flash or any other plugins. This is achieved through javascript, so I have used that to both generate the fractal and handle drawing operations. In this case, these are plotting the calculated colours of the fractal and drawing a zoom box overlay.

First, a <canvas> element is created. From here the special getContext can be called in javascript, which returns a rendering context object. This object makes the drawing operations available through a series of functions like arc, rect, fill, and stroke.

<canvas id="fractal"> </canvas>
...
var canvas = document.getElementById("fractal"); var zoom_canvas = document.getElementById("zoom_overlay"); var ctx = canvas.getContext("2d"); var canvasWidth = canvas.width; var canvasHeight = canvas.height; var canvasData = ctx.getImageData(0, 0, canvasWidth, canvasHeight);

Instead of plotting individual points to the canvas, it's faster to write a whole block of image data at once using putImageData, which takes a canvas image data object, returned above by getImageData. This will be called once the fractal has been calculated. It's convenient to get the width and height from the element directly, as means it can be defined just once in a stylesheet.

In calculating the fractal, a value for each point is calculated corresponding to a graph in which the Mandelbrot set appears: x between -2.5 and 1, and y between -1 and 1. I won't pretend to understand the maths involved using complex numbers here, but essentially we determine the value using a simple loop. I think it's amazing such a complex image comes from such a simple algorithm.

width=3.5; height=2;
xoffset=0; yoffset=0;

for (px=0; px < canvasWidth; px++) {
  for (py=0; py < canvasHeight; py++) {
      
    var x0 = (px / canvasWidth) * width + (xoffset - 2.5);
    var y0 = (py / canvasHeight) * height + (yoffset - 1);
    var x = 0;
    var y = 0;
    var iter = 0;
    var max_iter = 128;
      
    while ((x*x + y*y) < 4 && iter < max_iter) {
      var x_temp = x*x - y*y + x0;
      y = 2*x*y + y0;
      x = x_temp;
      iter++;
    }
      
    var rgb = hToRgb(iter/max_iter);
    drawPixel(px, py, rgb[0], rgb[1], rgb[2], 255);
  } 
}
ctx.putImageData (canvasData, 0, 0);

Two additional functions are used here: hToRgb and drawPixel.

By treating the output value as a hue (between 0 and 1), we get the familiar Mandelbrot colouring. Other colour mappings can be used to great effect, but this to me is the purest. The hToRgb function translates that hue into an rgb value so it can be written to the imagedata object. Many image libraries have a function like this, but javascript alone doesn't come with one

drawPixel simply puts the rgb data in a format appropriate for the imagedata object


// Draw single pixel to the imageData //
function drawPixel (x, y, r, g, b, a) {
    var index = (x + y * canvasWidth) * 4;

    canvasData.data[index + 0] = r;
    canvasData.data[index + 1] = g;
    canvasData.data[index + 2] = b;
    canvasData.data[index + 3] = a;
}

//Convert hue value to rgb
function hToRgb(h){
    if (h == 1)
      return [0,0,0];
    var r, g, b;
    var i = Math.floor(h * 6);
    var f = h * 6 - i;
    switch(i % 6){
        case 0: r = 1, g = f, b = 0; break;
        case 1: r = f, g = 1, b = 0; break;
        case 2: r = 0, g = 1, b = f; break;
        case 3: r = 0, g = f, b = 1; break;
        case 4: r = f, g = 0, b = 1; break;
        case 5: r = 1, g = 0, b = f; break;
    }
    return [r * 255, g * 255, b * 255];
}

There are faster and more accurate algorithms than this, which can produce a more detailed result. The level of detail can be tweaked here by adjusting the max_iter value, although I think this produces a nice balance of colouring with my flat hue to rgb mapping.

In my full demo, I also provided for the means to zoom in on the fractal. This is a achieved by overlaying another canvas and drawing a semi-transparent box on dragging the mouse. On release the fractal is redrawn with the new co-ordinates. This lets you explore the fractal in depth, which is part of the appeal! If you zoom in enough you will notice either distortion or edges rounding off. This is due to the accuracy of javascript's floating point calculations

Add a comment

captcha