12,507,089 members (63,727 online)
alternative version

28.4K views
25 bookmarked
Posted

# Generating Mandelbrot fractals using HTML5

, 24 Dec 2011 CPOL
 Rate this:
HTML5 offers a new cool element - Canvas. This is a replacement for SVG. You can draw on it with JavaScript, so we can draw fractals too.

View live demo

## Introduction

HTML5 offers a new cool element - `Canvas`. This is a replacement for SVG. You can draw on it with JavaScript. So in theory, we can draw fractals too? Yes, we can. This article is about drawing Mandelbrot with the HTML5 `Canvas` element. Most browsers support HTML5, Internet Explorer 9 too, but IE8 and lower don't support `Canvas`.

In order to draw, we have to get the context of the `Canvas`: `canvas.getContext('2d')`. The parameter "`2d`" is the name of the context. Most browsers support only 2D context, but Chrome has experimental WebGL for 3D context. Next, with the context element, we can draw and get or set image data. In this solution of Mandelbrot, we will use getting and setting image data, because we will draw pixel by pixel and it is faster than micro-rectangles.

## Background

I learned 2D context interface from this reference.

## Script Structure

The script is divided into two parts. There are formulas and a viewer. Formulas are single functions to calculate single pixels. Their inputs are coordinates (as complex from my library) and outputs are numbers between 0 and 1 (number of iterations divided by maximal iterations). The viewer part runs a specified formula for all coordinates in the `Canvas` and then represents them with a special palette. The viewer handles zooming too. Thanks to such a construction, it is extensible. We can simply add new formulas, for example, Julia.

## Mandelbrot Formula

Mandelbrot requires complex numbers. I wrote a simple class in JavaScript for these numbers. You can download it from the top of the article. So add the complex class to the page:

`<script type="text/javascript" src="complex.js"></script>`

Now we can define the function for Mandelbrot:

```function mandelbrot(xy) {
}```

`xy` will be a complex number. This function will be a formula. The algorithm for generating it is iterated. We will recalculate the `z` complex number in each iteration. Its start value is `xy`. The loop continues until the absolute value of `z` is less than the bailout. The bailout usually equals 4. In the center of the Mandelbrot, the number of iterations will be infinite. So we have to define the maximal iterations to avoid an infinite loop. Let's define `z` in the function and `i` for counting iterations:

`var z = new Complex(xy.x, xy.y);`

Now we have to define maximal iterations, bailout, and other numbers for recalculating `z`. Default offset and size will be declared too. They will be after the function.

```// Code of function ends.

mandelbrot.maxIter = 32; // Maximal iterations
// Power of fractal. For normal Mandelbrot it will be (2,0).
mandelbrot.power = new Complex(2.0, 0.0);
mandelbrot.bailout = 4.0; // Bailout value.
mandelbrot.offset = new Complex(-3.0, -2.0); // Default offset of fractal.
mandelbrot.size = new Complex(0.25, 0.25); // Default size of fractal.```

Now we can define the `while` loop of the formula:

```while (i < mandelbrot.maxIter && z.abs() <= mandelbrot.bailout) {
z = z.pow(mandelbrot.power).add(xy); // Recalculating z.
i++; // Iterations + 1.
}```

Now we can calculate the iterations. The return value of the function must be between 0 and 1. One is the maximal return value, and the max value of iterations is `maxIter`. So the return value will be `i` divided by maximal iterations. I apply a smoothing algorithm too.

```if (i < mandelbrot.maxIter) {
// Smoothing algorithm.
i -= Math.log(Math.log(z.abs())) / Math.log(mandelbrot.power.abs());
return i / mandelbrot.maxIter;
}
else
return 0.0;```

The function with its values should be like this:

```function mandelbrot(xy) {
var z = new Complex(xy.x, xy.y);
var i = 0;

while (i < mandelbrot.maxIter && z.abs() <= mandelbrot.bailout) {
i++;
}

if (i < mandelbrot.maxIter) {
i -= Math.log(Math.log(z.abs())) / Math.log(mandelbrot.power.abs());
return i / mandelbrot.maxIter;
}
else
return 0.0;
}
mandelbrot.maxIter = 32;
mandelbrot.power = new Complex(2.0, 0.0);
mandelbrot.bailout = 4.0;
mandelbrot.offset = new Complex(-3.0, -2.0);
mandelbrot.size = new Complex(0.25, 0.25);```

## The Viewer

First, let's create the body of the page:

```<h1>Mandelbrot</h1>
<div id="di" style="position: relative;">
<canvas id="cv" width="580" height="580"
style="border-style: solid; border-width: 1px;"
onmousedown="canvas_onmousedown(event);">
</canvas>
</div>```

`Div` is reserved for future use. Inside `div`, you can see a `Canvas` named `cv`. Now let's define the `generateFractal` function.

```function generateFractal(formula, resetSize) {
}```

The `formula` will be the function like our Mandelbrot formula. We will pass the formula function - not its value - to `generateFractal`. If `resetSize` is `true`, then offset and size variables will be set to default of fractal. Now add the Mandelbrot generation when the page is loaded.

`onload="generateFractal(mandelbrot);"`

Define the following global variables:

```var lastFormula; 	// Here we will store actual formula.
var tim; 		// In the future we will store here timeout for generating
// lines - it simulates asynchronous thread.
var offset = new Complex(-3.0, -2.0); 	// Offset of fractal view.
var size = new Complex(0.25, 0.25); 		// Scale of fractal view.```

When the first line is computed, we will set the timeout (1ms) for computing the second line. This 1ms is for refreshing the `Canvas`. `tim` will be the ID of the current timeout. First, we have to stop the previous calculation if it isn't completed. Then we have to set the new formula to `lastFormula`.

```clearTimeout(tim);
lastFormula = formula;```

Then we have to reset optionally offset and size:

```if (resetSize) {
offset = new Complex(formula.offset.x, formula.offset.y);
size = new Complex(formula.size.x, formula.size.y);
} ```

Then we have to get the `Canvas` and its width and height.

```var w = cv.width;
var h = cv.height;```

Now we have to get the context and the image data of the `Canvas`. Define `y` too. It will be the number of actually rendered lines.

```var g = cv.getContext("2d"); // Image context.
var img = g.getImageData(0, 0, w, h); // Image data.
var pix = img.data; // Table of canvas image data.```

Now declare the function `drawLine` in `generateFractal`.

```function drawLine() {
}```

Now if `y < h` (height of the `Canvas` is greater than 0):

```if (y < h)
tim = setTimeout(drawLine, 1);```

The nested function `drawLine` will compute a single line of fractal, will set the rendered part of the fractal to the `Canvas`, and then will set the timeout to it for the next line. Let's write this code. But first, we need the palette and a function for plotting the pixel. I will not explain how the palette works. Here is the function for getting the color from the formula's return value:

```function getColor(i) {
var k = 1.0 / 3.0;
var k2 = 2.0 / 3.0;
var cr = 0.0;
var cg = 0.0;
var cb = 0.0;
if (i >= k2) {
cr = i - k2;
cg = (k - 1) - cr;
}
else if (i >= k) {
cg = i - k;
cb = (k - 1) - cg;
}
else {
cb = i;
}
var r = parseInt(cr * 3 * 255);
var g = parseInt(cg * 3 * 255);
var b = parseInt(cb * 3 * 255);
return [r, g, b];
}```

Put it in the `generateFractal` function. Now let's define the function for drawing a pixel. It must have access to `pix` - the table of pixels. So put it inside the function for generating the fractal.

```function drawPixel(x, y, i) {
var c = getColor(i);
var off = 4 * (y * w + x);
pix[off] = c[0];
pix[off + 1] = c[1];
pix[off + 2] = c[2];
pix[off + 3] = 255;
}```

The first line gets the color by the formula's return value. In the second line, we calculate the starting position of the pixel to plot. Each pixel allocates 4 bytes (R, G, B, A) in the array. In the last 4 lines, it sets the bytes of the pixel. The last byte is 255, because the image isn't translucent (and this channel is alpha). Now you can write the `drawLine` function.

First, define the loop:

```for (var x = 0; x < w; x++) {
var c = formula(new Complex(x / w / size.x +
offset.x, y / h / size.y + offset.y));
drawPixel(x, y, c);
}```

In the variable `c`, we have calculated the value of the formula. It passes the scaled and translated position to the formula. Next, it sets the specified pixel in the `Canvas` to the returned value. When we have computed the line, we have to update the `Canvas`:

`g.putImageData(img, 0, 0);`

Now we have to set the timeout for computing the next line (if the number of the next line is less than the height of the `Canvas`).

```if (++y < h)
tim = setTimeout(drawLine, 1);```

## Julia Formula

Julia formula is very similar to Mandelbrot. There is one change: in the loop don't add pixel, but add seed. Seed is a point. It can be random or selected from Mandelbrot.

```function julia(xy) {
var z = new Complex(xy.x, xy.y);
var i = 0;

while (i < julia.maxIter && z.abs() <= julia.bailout) {
i++;
}

if (i < julia.maxIter) {
i -= Math.log(Math.log(z.abs())) / Math.log(julia.power.abs());
return i / julia.maxIter;
}
else
return 0.0;
}
julia.maxIter = 32;
julia.power = new Complex(2.0, 0.0);
julia.bailout = 4.0;
julia.offset = new Complex(-2.0, -2.0);
julia.size = new Complex(0.25, 0.25);
julia.seed = new Complex(0.0, 0.0);```

## Other Functionalities in the Demo

I have implemented zooming into a fractal too. It handles mouse events of the `Canvas` and the body. When you press a mouse button, it saves the first position, and when you hold a button, it saves the second position. It calculates the new offset and scale of the fractal. Additionally, when you move the mouse, it displays a preview - a red rectangle (`div` with absolute position).

In demo, there is switching too: you can choose point on Mandelbrot and this generates Julia fractal.

In order to save a fractal, I get the data URL of the `Canvas` and set it to an image. Then the user can save the image to disc. The data URL is the path with the data of the image as hex.

You can see the details of these functions in the source code.

## Conclusion

The HTML5 `Canvas` is a cool element for drawing dynamically using JavaScript. We can draw fractals with it too.

 Poland

## You may also be interested in...

 Pro Pro

 First Prev Next
 My vote of 5 Member 875456425-Sep-12 1:49 Member 8754564 25-Sep-12 1:49
 My vote of 5 Sergio Andrés Gutiérrez Rojas8-Jul-12 10:03 Sergio Andrés Gutiérrez Rojas 8-Jul-12 10:03
 My vote of 5 Monjurul Habib28-Dec-11 6:05 Monjurul Habib 28-Dec-11 6:05
 My vote of 5 Tech Code Freak24-Dec-11 19:15 Tech Code Freak 24-Dec-11 19:15
 Cool CIDev14-Dec-11 6:30 CIDev 14-Dec-11 6:30
 My vote of 5 Marcelo Ricardo de Oliveira1-Dec-11 4:17 Marcelo Ricardo de Oliveira 1-Dec-11 4:17
 My vote of 5 cx_jin22-Nov-11 13:10 cx_jin 22-Nov-11 13:10
 My vote of 5 Mihai Maerean21-Nov-11 6:49 Mihai Maerean 21-Nov-11 6:49
 my vote 4 Uday P.Singh18-Nov-11 19:35 Uday P.Singh 18-Nov-11 19:35
 My vote of 5 Amir Eshaq18-Nov-11 3:58 Amir Eshaq 18-Nov-11 3:58
 Fun stuff Olivier Giulieri17-Nov-11 12:39 Olivier Giulieri 17-Nov-11 12:39
 Last Visit: 31-Dec-99 18:00     Last Update: 28-Sep-16 0:37 Refresh 1