Part IV

Part IV: The IGenerator Interface And The Mandelbrot Class

It is now time to describe how the Fractals are actually generated. The process can be split in two tasks:

-Deciding if a given point belongs to the Fractal set.
-Deciding wich color to assign to that point.

The IGenerator interface defines the methods that every generator must expose:

public interface IGenerator
{
    IGenerator
CreateGenerator();
    string
GetName();
    Bitmap
Draw(Domain domain, int resx, int resy);
    Domain
GetDomain();
    void
SetDomain(Domain domain);
}

CreateGenerator() is the factory method I already introduced. GetName() simply returns the name of a specific implementation. Draw() does the actual computation of the Fractal and returns a color for the specified point. GetDomain() and SetDomain() are quite obvious. Note that the Draw() routine does not use the same domain specified by SetDomain(), but uses the one provided as a parameter in order to support multithreading, where each task has its own sub-domain.

Mandelbrot

The first Fractal implementation is Mandelbrot:

public class Mandelbrot : IGenerator
{
    #region
Generator Members

    protected Domain domain;
    protected
int maxIterations;

    public Mandelbrot()
    {
        this
.domain = new Domain(-2.0f, 1.5f, 3.0f, 3.0f);
        this
.maxIterations = 200;
    }

The default domain has been choosen so that the whole Mandelbrot shape is drawn. MaxIterations defines how many times the formula must be applied before deciding that the given point is part of the set. The higher is this value, the higher is the precision of the solution, but rendering times increase as well. The default value is a good compromise between precision and speed.

    public virtual Bitmap Draw(Domain domain, int resx, int resy)
    {
        Bitmap
bmp = new Bitmap(resx, resy);

        double dx = domain.Width / resx;
        double
dy = domain.Height / resy;

        for (int y = 0; y < resy; ++y)
        {
            double
py = domain.Y - dy * y;
            for
(int x = 0; x < resx; ++x)
            {
                double
px = domain.X + dx * x;

                Color c = ComputePoint(new Complex(px, py));

                bmp.SetPixel(x, y, c);
            }
        }

        return bmp;
    }

The Draw() routine is straighforward: it simply iterates through the pixels and call ComputePoint() for each of them.

    protected virtual Color ComputePoint(Complex point)
    {
        int
iterations = 0;

        Complex z = point;   
        while
((Complex.ModulusSquared(z) < 4.0) && iterations < this.maxIterations )
        {   
            iterations++;
            z = z * z + point;
        }

        if (iterations == this.maxIterations)
        {
            return
Color.Black;
        }
        else
        {
            return
Shade(iterations, z);
        }
    }

Having a different method to compute the pixel value can be useful because this way you can derive a child class from Mandelbrot and override the ComputePoint() method leaving most of the other code unchanged. This code performs the actual computation.
The z Complex Number is initialized with the value of the current point. At each iteration the Mandelbrot formula is applied to get a new z value until either the modulus of z becomes bigger than 2 or the iterations counter reaches the maximum allowed iterations threshold. For performance reasons I compare the squared modulus to 4 instead than the modulus to 2, but they are equivalent.

Once the loop ends, we assign a color. If the point belongs to the set then black is returned. Otherwise a shading method is invoked.
There is not just one way to give a color to a point. Actually there are many, and you decide which one to use by functional or aesthetical considerations. The Shade() method, just like ComputePoint(), has beed defined as virtual so that derived classes can override it and not much else in order to have the same Fractal with different shading. I will show three different implementations of this method.

    protected virtual Color Shade(int iterations, Complex z)
    {
        //mu = N + 1 - log (log |Z(N)|) / log k
        double
smooth = iterations + 1.0 - Math.Log(Math.Log(Complex.Modulus(z))) / Math.Log(2);
        double
val = (smooth / maxIterations);

        double dx=0.8;               
        double ff = Clamp(val, 0.0, 1.0); //clamp f in [0,1]
        double
g = (float)(6.0 - 2.0 * dx) * val + dx; //scale f to [dx, 6-dx]
        double
R = Math.Max(0.0, (3.0 - Math.Abs(g - 4.0) - Math.Abs(g - 5.0)) / 2.0);
        double
G = Math.Max(0.0, (4.0 - Math.Abs(g - 2.0) - Math.Abs(g - 4.0)) / 2.0);
        double
B = Math.Max(0.0, (3.0 - Math.Abs(g - 1.0) - Math.Abs(g - 2.0)) / 2.0);

        return Color.FromArgb((int)(R * 255), (int)(G * 255), (int)(B * 255));
    }

I will not go into details here. This method produces smooth transitions between colors. The iterations count and the last z values are used to modulate the color.

Here is another shading method:

    protected override Color Shade(int iterations, Complex z)
    {
        double
red = (iterations % 32) * 7;
        double
green = (iterations % 16) * 14;
        double
blue = (iterations % 128) * 2;

        return Color.FromArgb((int)red, (int)green, (int)blue);
    }

This is simpler and only uses the iterations count. This is a quite standard method to shade the pixels and produces nice results, The red, green and blue component are only required to be in the range [0,255] but are otherwise free from any constraint. The choosen formulas are only one of the possible combinations, and you are free to play with them and see how the output changes.

The following code is similar to the previous one, but produces a greyscale image. I like it, so I decided to add it.

   protected override Color Shade(int iterations, Complex z)
    {
        int
val = (int)(((double)iterations / (double)this.maxIterations) * 255);
        return
Color.FromArgb(val, val, val);
    }

A more sophisticated way to generate colors would be to use other color spaces: for example HSV provides an easy way to map from a scalar to a color without the need for all the hacks seen before. Converting from RGB to HSV and the way around is not hard, but doing so would have added little value to the tutorial, at expense of more code to show and explain.

The Julia Set

If we slightly change the Mandelbrot equation so that we add a constant at each iteration instead than the original point we get the Julia Set:

    protected virtual Color ComputePoint(Complex point)
    {
        int
iterations = 0;
        Complex
z = point;
        Complex
k = new Complex(-0.74434, -0.10772);

        while ((Complex.ModulusSquared(z) < 4.0) && iterations < this.maxIterations)
        {
            iterations++;
            z = z * z + k;
        }
        if
(iterations == this.maxIterations)
        {
        return
Color.Black;
        }
        else
        {
            return
Shade(iterations, z);
        }
    }

The k constant can be set to any value, and the resulting image will be completely different. A nice feature to add to the application would be the option to set parameters for the algorithms. For this tutorial I choose those values because they result in a very nice Fractal. On the Web you can find more nice constants.

The Julia Set has a slightly different default domain compared to Mandelbrot:

    this.domain = new Domain(-1.5f, 1.5f, 3.0f, 3.0f);

The Newton Fractal

There is one more Fractal available in the application: the Newton Fractal. I will not describe it here because the idea behind it is not far from what already explained and because the mathematic is a bit more advanced. The implementation, however, is not much complex.

A short description however is useful. If we use Complex Numbers for evaluating the roots of a polynomial of degree n, then we have exactly n complex solutions (roughly said). An algorithm called Newton Method allows to approximate the roots of a polynomial and we can create a Fractal by applying that method to a point in the Complex Plane and see which root it converges to. The polynomial used in my implementation is

f(z) = z^3 -1

This explains why we have those three 'chains': we have three roots because this polynomial is of degree 3.
There are many other polynomials that can be used to generate interesting Fractals. For some examples, see http://en.wikipedia.org/wiki/Newton_Fractal.