# Simplifying zoom box implementation

Building on information presented in Mapping screen coordinates to the complex plane, here we look at how to rewrite Mandelbrot 1 in order to simplify the implementation of the zoom box feature.

For performance reasons and because the coordinates of a zoom box (upper-left and lower-right corners) will always be in canvas screen coordinates, we rewrite Mandelbrot 1 as follows:

```
<!DOCTYPE html>
<html>
<head>
<meta content="text/html; charset=utf-8" http-equiv="Content-Type">
<title>Mandelbrot 2</title>
<style>
html, body {
margin: 0;
padding: 0;
text-align: center;
}
canvas {
border: 1px black solid;
}
</style>
</head>
<body>
<h1>Mandelbrot 2</h1>
<p>This example demonstrates an algorithm for drawing the Mandelbrot set using canvas screen coordinates.</p>
<canvas width="600" height="400">Canvas not supported - upgrade your browser</canvas>
<script>
var RE_MAX = 1.1; // This value will be adjusted as necessary to ensure that the rendered Mandelbrot set is never skewed (that is, true to it's actual shape).
var RE_MIN = -2.5;
var IM_MAX = 1.2;
var IM_MIN = -1.2;
var MAX_ITERATIONS = 300; // Increase to improve detection of complex c values that belong to the Mandelbrot set.
var globals = {}; // Store all would-be-global-variables in one handy global object.
globals.canvas = document.getElementsByTagName('canvas')[0];
globals.canvas.ctx = globals.canvas.getContext('2d');
globals.canvas.ctx.fillStyle = "black"
drawMandelbrotSet(RE_MAX, RE_MIN, IM_MAX, IM_MIN);
function drawMandelbrotSet(ReMax, ReMin, ImMax, ImMin) {
var canvasWidth = globals.canvas.width; // A small speed optimization.
var canvasHeight = globals.canvas.height; // A small speed optimization.
ReMax = canvasWidth * ( (ImMax - ImMin) / canvasHeight ) + ReMin; // Make the width and height of the complex plane proportional to the width and height of the canvas.
if (RE_MAX != ReMax) {
alert("RE_MAX has been adjusted to: " + ReMax); // The user should never see this if RE_MAX is set correctly above.
} // if
var ctx = globals.canvas.ctx;
var x_coefficient = (ReMax - ReMin) / canvasWidth; // Keep the Mandelbrot loop as computation-free as possible.
var y_coefficient = (ImMin - ImMax) / canvasHeight; // Keep the Mandelbrot loop as computation-free as possible.
for (var x = 0; x < canvasWidth; x++) {
var c_Re = (x * x_coefficient) + ReMin // Convert the canvas x-coordinate to a complex plane Re-coordinate. c_Re represents the real part of a c value.
for (var y = 0; y < canvasHeight; y++) {
var c_Im = (y * y_coefficient) + ImMax; // Recall that c = c_Re + c_Im*i
var z_Re = 0; // Recall that the first z value (Zo) must be 0.
var z_Im = 0; // Recall that the first z value (Zo) must be 0.
var c_belongsToMandelbrotSet = true;
for (var iterationCount = 1; iterationCount <= MAX_ITERATIONS; iterationCount++) {
var z_Re_squared = z_Re * z_Re; // A small speed optimization.
var z_Im_squared = z_Im * z_Im; // A small speed optimization.
// The next two lines perform Zn+1 = (Zn)^2 + c (note that (x + yi)^2 = x^2 - y^2 + 2xyi, thus the real part is x^2 - y^2 and the imaginary part is 2xyi).
z_Im = (2 * z_Re * z_Im) + c_Im; // We must calculate z_Im first because it's a function of z_Re.
z_Re = z_Re_squared - z_Im_squared + c_Re; // The is not a function of z_Re.
if ( z_Re_squared + z_Im_squared > 4 ) { // Checks if |z^2| is greater than 2.
c_belongsToMandelbrotSet = false; // This complex c value is not part of the Mandelbrot set.
break; // So we immediately check the next c value.
} // if
} // for
if (c_belongsToMandelbrotSet) {
ctx.fillRect(x, y, 1, 1); // This c value is probably part of the Mandelbrot set, so set the color of the associated pixel to black. Increase MAX_ITERATIONS to increase the probability.
} // if
} // for
} // for
} // drawMandelbrotSet
</script>
</body>
</html>
```

The first thing to notice is that the complex class has been replaced with more performant in-loop calculations. For example, the expensive square root operation associated with `z.modulus() > 2`

has been replaced with `z_Re_squared + z_Im_squared > 4`

in that:

A number of other small optimizations have been made in order to remove as many calculations as possible from the three (triply-nested) `for`

loops as indicated by the comments in the previous code example.

Next, recall that our transformation equations assume proportionality (see Mapping screen coordinates to the complex plane). The line:

```
ReMax = canvasWidth * ( (ImMax - ImMin) / canvasHeight ) + ReMin;
```

ensures that the width and height of the complex plane are proportional to the width and height of the canvas. Without this check, it's possible to choose a value for `RE_MAX`

that would break the proportionality assumption, resulting in a skewed image of the Mandelbrot set.

The major difference between Mandelbrot 1 and Mandelbrot 2, however, is the fact that each canvas pixel is now considered to be a *c* value:

```
for (var x = 0; x < canvasWidth; x++) {
var c_Re = (x * x_coefficient) + ReMin // Convert the canvas x-coordinate to a complex plane Re-coordinate. c_Re represents the real part of a c value.
for (var y = 0; y < canvasHeight; y++) {
var c_Im = (y * y_coefficient) + ImMax; // Recall that c = c_Re + c_Im*i
...
```

Here we loop through each canvas pixel (`x`

, `y`

) and construct the associated (coincident) *c* point (`c_Re`

, `c_Im`

) in the complex plane using the coordinate transformation equations described in Mapping screen coordinates to the complex plane.

Next, we construct *z*₀ (which must always be 0) to determine if *c* is in the Mandelbrot set or not by observing the behavior of *z* under iteration of *z*ₙ₊₁ = *z*ₙ + *c*:

```
var z_Re = 0; // Recall that the first z value (Zo) must be 0.
var z_Im = 0; // Recall that the first z value (Zo) must be 0.
var c_belongsToMandelbrotSet = true;
for (var iterationCount = 1; iterationCount <= MAX_ITERATIONS; iterationCount++) {
var z_Re_squared = z_Re * z_Re; // A small speed optimization.
var z_Im_squared = z_Im * z_Im; // A small speed optimization.
// The next two lines perform Zn+1 = (Zn)^2 + c (note that (x + yi)^2 = x^2 - y^2 + 2xyi, thus the real part is x^2 - y^2 and the imaginary part is 2xyi).
z_Im = (2 * z_Re * z_Im) + c_Im; // We must calculate z_Im first because it's a function of z_Re.
z_Re = z_Re_squared - z_Im_squared + c_Re; // The is not a function of z_Re.
if ( z_Re_squared + z_Im_squared > 4 ) { // Checks if |z^2| is greater than 2.
c_belongsToMandelbrotSet = false; // This complex c value is not part of the Mandelbrot set.
break; // So we immediately check the next c value.
} // if
} // for
if (c_belongsToMandelbrotSet) {
ctx.fillRect(x, y, 1, 1); // This c value is probably part of the Mandelbrot set, so set the color of the associated pixel to black. Increase MAX_ITERATIONS to increase the probability.
} // if
```

To explain this code fragment, consider the following expansion of the Mandelbrot recurrence relation:

Because *z*ₙ₊₁ = *A*ₙ + *B*ₙ*i*, the real *A*ₙ and *B*ₙ values can be used to calculate *z*ₙ₊₂ as follows:

Furthermore, we can use the values of *A*ₙ and *B*ₙ to calculate *A*ₙ₊₁ and *B*ₙ₊₁, as follows:

And using *A*ₙ₊₁ and *B*ₙ₊₁, *z*ₙ₊₃ is calculated as above:

This inductive argument can be extended indefinitely to calculate as many *z* values as required, and in particular, explains the previous two lines, which are repeated here:

```
z_Im = (2 * z_Re * z_Im) + c_Im;
z_Re = z_Re_squared - z_Im_squared + c_Re;
```

That is, the first line is equivalent to:

And the second to:

Be aware that if `z_Re`

where calculated before `z_Im`

(that is, if the previous two lines were switched), the formula for `z_Im`

would not be using the current value of `z_Re`

, but the next value of `z_Re`

, producing incorrect results.

Lastly, if the absolute value of *z*ₙ doesn't tend towards infinity (that is, `z_Re_squared + z_Im_squared <= 4`

) within `MAX_ITERATIONS`

, then the associated *c* value, that is the point (`x`

, `y`

) in the canvas coordinate system representing *c*, is (most likely) part of the Mandelbrot set and we paint the pixel at (`x`

, `y`

) black. Otherwise, (`z_Re_squared + z_Im_squared > 4`

) and *c* is not part of the Mandelbrot set, and the pixel at (`x`

, `y`

) remains white.

Now that we have a means of converting canvas screen coordinates to the complex plane, we're in a position to implement zoom box functionality, as discussed in Implementing a zoom box.