# 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*0 (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*n+1 = *z*n + *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*n+1 = *A*n + *B*n*i*, the real *A*n and *B*n values can be used to calculate *z*n+2 as follows:

Furthermore, we can use the values of *A*n and *B*n to calculate *A*n+1 and *B*n+1, as follows:

And using *A*n+1 and *B*n+1, *z*n+3 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*n 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.

## Related topics