Manbelbrot Meets HTML5 - A Little Fractal Geometry

I have been fascinated by Mandelbrot fractals ever since I watched the BBC documentary about Benoit Mandelbrot years ago. First time I wrote a program to generate a Mandelbrot set was when I was around 14 years old. I was astounded to see an image of infinite depth rendered using such simple rules.

Recently, while reading about HTML5's Canvas element, I thought of how hard it would be to rewrite my old program in HTML5. Turns out, it was not very hard at all. It took me about 2 hours to come up with this first revision.

Please ensure your browser supports HTML5 Canvas element.

And here is the source code. It includes a very minimal implementation of a Complex class with only addition and multiplication, which are the only operations used for the Mandelbrot fractal. Remember that the key equation here is \(f(z) = z^2+c\).

function Complex(real, imaginary) {
    this.a = real;
    this.b = imaginary;
}

Complex.prototype.abs = function() {
    return Math.sqrt(this.a * this.a + this.b * this.b);
};

Complex.prototype.toString = function() {
    return "(" + this.a + "," + this.b + ")";
};

Complex.add = function(a, b) {
    return new Complex(a.a + b.a, a.b + b.b);
};

Complex.multiply = function(a, b) {
    return new Complex(a.a * b.a - a.b * b.b, a.a * b.b + a.b * b.a);
};

Complex.prototype.add = function(b) {
    return Complex.add(this, b);
};

Complex.prototype.multiply = function(b) {
    return Complex.multiply(this, b);
};


function MandelbrotPixelValue(z, c, nlimit) {
    rmax = 2; // Max value before deciding the point converges to infinity
    var j = 0;
    for (; j < nlimit && z.abs() < rmax; j++) {
        z = z.multiply(z);
        z = z.add(c);
    }
    j *= 3;
    return {
        r: 240,
        g: j % 255,
        b: 0,
        a: 0xff
    };
}

function Fractal(canvasElement, pixelValueFunction) {
    this.pixelValueFunc = pixelValueFunction;
    this.element = canvasElement;
}

// start indicates where in the complex plane to start, must be of type Complex
// z is the fractal's constant
Fractal.prototype.DrawFractal = function(start, inc, z, nlimit, options) {
    startTime = new Date().getTime();
    context = this.element.getContext("2d");
    width = parseInt(this.element.getAttribute("width"));
    height = parseInt(this.element.getAttribute("height"));
    imageData = context.createImageData(width, height);

    var x, y;
    var xend = start.a + width * inc;
    var yend = start.b + height * inc;

    var i = 0,
        j = 0,
        c = 0;

    for (y = start.b; y < yend && j < height; y += inc) {
        i = 0;
        for (x = start.a; x < xend && i < width; x += inc) {
            color = this.pixelValueFunc(z, new Complex(x, y), nlimit);
            imageData.data[c++] = color.r;
            imageData.data[c++] = color.g;
            imageData.data[c++] = color.b;
            imageData.data[c++] = color.a;
            i++;
        }
        j++;
    }

    context.putImageData(imageData, 0, 0);
    processingTime = new Date().getTime() - startTime;
    context.fillStyle = '#fff';
    context.font = '10px courier';
    context.textAlign = 'left';

    var line = 0;
    var heightDelta = 12;
    var xstart = 10;

    if (options.display.processingTime) {
        context.fillText("Processing time: " + processingTime + "ms", xstart,
            line += heightDelta);
    }
    if (options.display.parameters) {
        context.fillText("Limit: " + nlimit + "", xstart, line += heightDelta);
        context.fillText("Start: " + start.toString() + "", xstart, line += heightDelta);
        context.fillText("Constant: " + z.toString() + "", xstart, line += heightDelta);
        context.fillText("Max Magnitude: " + rmax + "", xstart, line += heightDelta);
    }
};

Comments