Most mathematical formulae when plotted produce wave-like lines, curves, arcs and other simple shapes. Fractals by contrast produce extremely complex lacy colourful patterns. You never really see the same thing twice. They are often natural looking, imitating patterns produced in nature such as ferns or coastlines. Despite this, they are extremely simple to program, as we'll see here.
What is a fractal?
A fractal can be described as "the adventures of a small number in a complex plane." A fractal is a peculiar and very dense graph generated by a mathematical process. Although the graph are infinitely complex, the underlying algorithms are short and relatively simple.
When you want to see the relationship between numbers, to see mathematical expressions, you can put them into a chart or plot. A simple plot is shown below, distance against elevation.
By going along the line you can see the altitude of various points change with distance.
Not all numbers used my mathematicians are those we are familiar with. For example to translate waveforms into audio, or to analyse radio signals it is necessary to use "imaginary" numbers.
Complex and Imaginary Numbers
In the 16th century, mathematicians began trying to describe the square root of minus one. When you multiply a number by itself, you square it; 4 * 4 is 16. You can describe this relationship in the opposite way, 4 is the square root of 16. It seems obvious that there is a square root for any number. Squares and roots have been used for thousands of years, but attention was focused on -1 which although paradoxical, eventually proved quite useful.
What is the square root of -1? When you square a positive number, you always get a positive number. When you multiply a negative number by a negative number, you also get a positive number. So, the number you need to multiply by itself to get -1 cannot be positive, nor can it be negative. In other words it is not a real number, it is imaginary.
When you combine imaginary numbers with real nu,bers, you get points on a grid. Real numbers describe the horizontal axis, while imaginary numbers describe the vertical distance. This creates a complex number, and they are an important concept, used in almost every field of mathematics and applied science, from voice processing, image compression, radar analysis, even earthquake monitoring.
A fractal is built pixel by pixel via the repeated transformation of a complex number. Several things can happen when you transform a complex number by multiplying it by itself.
- If it is a real number larger than 1, it will grow towards an infinitely large number.
- If it is less than one (a fraction) it will shrink towards an infinitely small number close to zero, but never reaching zero.
- If it is exactly one, it will remain one. 1 * 1 is 1 and always will be.
The Mandelbrot Set
All numbers that are negative are described as a "set" of negative numbers. The Mandelbrot set is a group of numbers which when plugged into the Mandelbrot equation remain bounded, that is they don't escape to infinity. The numbers which move towards zero, or those which oscillate, when painted in, create the classic Mandelbrot shape.
We want to display these fractals on the screen. We can think of the complex plane as a grid, with real numbers shown horizontally and imaginary numbers shown vertically. To calculate the Mandelbrot set, we'll first choose a zone within the grid to paint in. For each point in this area we assign the corrsponding complex number to c and starting with z = 0, we repeat the transformation against this value enough times to discover whether or not this particular position will escape to infinity. it if will, we colour it white to show it is outside of the Mandelbrot set. If it won't escape we colour it black to show that it is part of the set. The number of times we run this test is the number of iterations.
The result is an intricate shape known as the Mandelbrot set. The irregular line between the black and the white areas is the boundary between the two attractors (infinity and zero). As you can see, it is by no means as simple as a circle.
Programming the Mandelbrot Set
Let's first start with the black and white Mandelbrot set. The only parameters to define are:
- The area of the complex plane (xMin, xMax, yMin, yMax)
- The resolution of the image (nx, ny)
- The maximum iterations required to determine if the value escapes or is bounded (maxIter)
Thirty two iterations is a good starting point. Larger values produce more accurate results, however require more calculation time.
I know this is in VisualBasic, it's the language I was using at the time. Later examples use Delphi and Pascal.
Sub GenerateMAndelbrot () BLACK = RGB(0,0,0): WHITE = RGB(255,255,255) Dim X As Double, Y As Double Dim ix As Integer, iy As Integer Dim MaxIter As Integer ' Define picture resolution nx = 256 ny = 256 ' Select area of the complex plane to map xmin = -1.5 xmax = .5 ymin = -1 ymax = 1 ' Maximum number of iterations MaxIter = 32 ' For each point in the selected area For iy = 0 To ny - 1 Y = (ymin + iy * (ymax - ymin) / (ny - 1)) For ix = 0 To nx - 1 X = (xmin + ix * (xmax - xmin) / (nx - 1)) ' Now check if it escapes to infinity M - Mandelbrot(X, Y, MaxIter) If M = MaxIter Then Picture1.PSet(ix,iy), BLACK Else Picture1.PSet(ix,iy), WHITE End If Next DoEvents Next End Sub Function Mandelbrot (ByVal cx As Double, ByVal cy As Double, ByVal MaxIter As Integer) As Integer Dim x2 As Double, y2 As Double Dim temp As Double Dim X As Double, Y As Double X = 0 Y = 0 x2 = 0 y2 = 0 iter = 0 While ((iter < MaxIter) And (Abs(x2) + Abs(y2) < 100000)) temp = x2 - y2 + cx Y = 2 * X * Y + cy X = temp x2 = X * X y2 = Y * Y iter = iter + 1 Wend Mandelbrot = iter End Function
The first function essentially goes through each pixel in the complex plain and runs the Mandelbrot calculation. If the value returned from this function does not escape to infinity it assigns that pixel the black colour, while if it does escape to infinity it is assigned the WHITE colour.
The actual calculation is done by the Mandelbrot function. The loop tests to see if the calculated value from the Mandelbrot equation escapes to infinity before the maximum iterations is reached. If it is, it returns the number of iterations, if it doesn't then it returns the max iterations. For this example, the value of 100000 is good enough to represent infinity.
Black and White fractals are all well and good, but coloured fractals are often breathtaking. Where does the colour come from?
Different colours represent different escape velocities. That is, the calculation not only tests to see if a point escapes to infinity, it also tests how fast it escapes. These mysterious variations of escape velocity give fractals their beauty and complexity.
These code examples show code written in Borland Delphi. The fully working example can be downloaded at the end of the article.
procedure TfrmMain.DrawMandel; var x,y: real; ix,iy,nx,ny,ipx,px: integer; aColor: TColor; begin nx := frmMain.FractalImage.Width; ny := frmMain.FractalImage.Height; DRAWINGF := TRUE; px := 0; begin nx := frmMain.FractalImage.Width; ny := frmMain.FractalImage.Height; for iy := 0 to ny-1 do begin if STOPNOW = TRUE then break; Y := (ymin +iy * (ymax -ymin) / (ny - 1)); for ix := 0 to nx-1 do begin X := (xmin + ix * (xmax - xmin) / (nx - 1)); aColor := CalcColorMandel(X,Y,maxiter); frmMain.FractalImage.Canvas.Pixels[ix,iy] := aColor; frmMain.Progress.Progress := trunc(((iy/frmMain.FractalImage.Height)*100)+1); frmMain.ProgressShow.Progress := frmMain.Progress.Progress; end; Application.ProcessMessages; end; end; DRAWINGF := FALSE; STOPNOW := FALSE; btnStop.Visible := FALSE; btnDraw.Visible := TRUE; px := 64; end; function TfrmMain.CalcColorMandel(cx,cy: double; maxiter: integer): TColor; var x2,y2,temp,x,y : double; iter: integer; begin x := 0; y := 0; x2 := 0; y2 := 0; iter := 0; while ((iter < maxiter) and ((abs(x2) + abs(y2)) < 100000)) do begin temp := x2 - y2 + cx; y := (2 * x * y) + cy; x := temp; x2 := x * x; y2 := y * y; inc(iter); end; if PaletteType = 2 then result := trunc((iter+80)*colorFactor); if PaletteType = 1 then begin if ((iter > 60) AND (iter < maxiter)) then result := 255 * 255 * 255 else if ((iter < 56) AND (iter > 53)) then result := 240 * 255 * 255 else if ((iter < 52) AND (iter > 47)) then result := 244 * 255 * 255 else if ((iter < 48) AND (iter > 45)) then result := 192 * 255 * 255 else if ((iter < 44) AND (iter > 41)) then result := 176 * 255 * 255 else if ((iter < 40) AND (iter > 37)) then result := 160 * 255 * 255 else if ((iter < 36) AND (iter > 33)) then result := 144 * 255 * 255 else if ((iter < 32) AND (iter > 29)) then result := 128 * 255 * 255 else if ((iter < 28) AND (iter > 25)) then result := 112 * 255 * 255 else if ((iter < 24) AND (iter > 21)) then result := 96 * 255 * 255 else if ((iter < 20) AND (iter > 17)) then result := 80 * 255 * 255 else if ((iter < 16) AND (iter > 13)) then result := 64 * 255 * 255 else if ((iter < 12) AND (iter > 9)) then result := 32 * 255 * 255 else if ((iter < <img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAACf0lEQVQ4y4WRX0hTcRTHDxVBSyjBFHvpDyxtFKkz/AsNJmU2JtKs1IdAqLAifJB6qZcoEiMKUnJKiUo4CqIFEtXuNLWkRRGGkIJZjmU5sSUW3UvtdL6/66SHoAtfvvec8/l+X3401UUU7iaa7iH67CPLzB1yi7yigCi06JjduIMDjxwzmz+LBVaRby6YO6Z/OB/7Pdtp8Py9OBwz9riD+1eBNeJbpX0fPRblWS/zjCjayTzbbTpm2eMODvxSwcRNskx2kG9hpCb66WkVP2ncrBQZOsHx6RvKEzvcwYFHThWMt5E78iBzPP6+jmMvPBx+XM7hgIe/varj+OQ55Zixxx0ceORUwWgLea80ZMbstrXc2nyJXS4XW1I2cFZmMl+u36b8YtNVtc/KWMPgwCOnCt5co4D+tsoozEnjQxXF7HQ6OSO7iPN3pPGU33Sny6P2jryNDA48cqog1EShXyOVce8ZG+8vsy8ql7Xrdp57VKgcc+LWenorg0dOFQxdoMCP4TLj68Od/PFutihHZOcFrZiNQYdyzOY+m8GBR04VBM6S90tvQUzX8lnvK2Kj38HGQImEd4v2mI5Z9riDA4+cKrhfT+6hxtRxvW8X60GHeq55bR8bwwdEB01/VsH6wF51BwceOVVw+whZumrJ9+7W9qjeX8ozvaVL755Q2F8qJZWMOzjwyKmC9hqitmqydhxeoY2226P68xo2Xh5l4/VJ0SnTZcYed3DgkVMF+LakEtU7yNpSST5/Q/rYRE9JLBasNn4O18bhmLHHHRx4fImC5SKsbClJVODJouNN5dTV7CFNAiE4ZuzlnifcJlHS3wXLRKtF60Tr/6N0UbJoZaLgD4alKxSgG6GYAAAAAElFTkSuQmCC" alt="8)" title="8)" width="16" height="16" style="top:3px;position:relative" /> AND (iter > 5)) then result := 16 * 255 * 255 else if (iter < 4) then result := 0 else result := 0; end; end;
What we see here is the colour method testing how many iterations were needed before the value escapes to infinity. Based on this it assigns a colour value (R*G*B)
You can download the code from GitHubDownload from GitHub