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.
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 .
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