Draw a Buddhabrot fractal in C#

This example shows how to draw a Buddhabrot fractal, a fractal curve that is somewhat similar to the Mandelbrot set. To draw a Mandelbrot set, you use complex numbers to iterate the function Z = Z2 + C for various values of C. It can be shown that, if the magnitude of Z ever grows beyond 2, then the function eventually heads towards infinity.

To draw a Mandelbrot set, you iterate the function and see how many iterations it takes for the function to reach magnitude 2. You then color the point C based on how many iterations it took. For example, if you use N colors and it took M iterations, then you could give the point color number M mod N.

To draw a Buddhabrot fractal, you iterate Z = Z2 + C for values as usual, except you pick the values C randomly. If the function’s magnitude exceeds 2 at some point, you go back and iterate the function again. This time you increment a count for each value Z that you come to before the magnitude exceeds 2. When you’re done, you set a pixel’s brightness to be 255 times its hit count divided by the largest hit count of any pixel.

To get the colorized version, you set each point in the sequence based on the color of the initial value C. In this example, the program makes the points red if the distance from C to the origin is less than 1, green if the distance from C to the origin is less than √2, and blue otherwise.

The following code shows the main DrawBrot method.

// Draw the Buddhabrot until stopped or
// we plot the desired number of points.
private void DrawBrot()
{
    // Get parameters.
    int wid = int.Parse(txtWidth.Text);
    int hgt = int.Parse(txtHeight.Text);
    int cut_r = int.Parse(txtRedCutoff.Text);
    int cut_g = int.Parse(txtGreenCutoff.Text);
    int cut_b = int.Parse(txtBlueCutoff.Text);
    int stop_after = int.Parse(txtStopAfter.Text);
    int draw_every = int.Parse(txtDrawEvery.Text);

    if ((wid <= 0) || (hgt <= 0))
    {
        MessageBox.Show("Invalid parameter", "Error",
            MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
        return;
    }

    // Make hit count arrays.
    int[,] hit_r = new int[wid, hgt];
    int[,] hit_g = new int[wid, hgt];
    int[,] hit_b = new int[wid, hgt];

    // Make the bitmap.
    BrotBitmap = new Bitmap(wid, hgt);
    picCanvas.Image = BrotBitmap;
    this.ClientSize = new Size(
        picCanvas.Left + picCanvas.Width + 8,
        System.Math.Max(
            btnDraw.Top + btnDraw.Height,
            picCanvas.Top + picCanvas.Height) + 8);
    mnuFileSave.Enabled = true;

    // Start drawing.
    DateTime start_time = DateTime.Now;
    DateTime stop_time;
    TimeSpan elapsed;
    IsDrawing = true;

    // Build the hit counts.
    double dx = (Wxmax - Wxmin) / hgt;
    double dy = (Wymax - Wymin) / wid;
    int max_r = 0, max_g = 0, max_b = 0, hits = 0, total_hits = 0;

    while (total_hits < stop_after)
    {
        double cx = Wxmin + Rand.NextDouble() * (Wxmax - Wxmin);
        double cy = Wymin + Rand.NextDouble() * (Wymax - Wymin);
        double dd = cx * cx + cy * cy;
        if (dd < 1)
        {
            DrawPoint(cx, cy, wid, hgt, dx, dy,
                ref max_r, hit_r, cut_r, ref hits);
        }
        else if (dd < 2)
        {
            DrawPoint(cx, cy, wid, hgt, dx, dy,
                ref max_g, hit_g, cut_g, ref hits);
        }
        else
        {
            DrawPoint(cx, cy, wid, hgt, dx, dy,
                ref max_b, hit_b, cut_b, ref hits);
        }

        if (hits >= draw_every)
        {
            total_hits += hits;
            hits = 0;
            DisplayBrot(wid, hgt, max_r, max_g, max_b,
                hit_r, hit_g, hit_b);

            stop_time = DateTime.Now;
            elapsed = stop_time.Subtract(start_time);
            this.Text = elapsed.TotalSeconds.ToString("0.00") + 
                " sec, " + total_hits.ToString() + " hits";

            Application.DoEvents();
            if (!IsDrawing) break;
        }
    }
}

This method gets the input parameters you entered. It then makes arrays to record the number of hits for each pixel in the result, and it makes the result bitmap.

The code then enters a loop that runs until the specified maximum hit count is reached (or you stop the program). Inside the loop, the method picks a random C value. Depending on how far the value is from the origin, the code calls the DrawPoint method passing it parameters that indicate which color to give the point.

If the number of hits is a multiple of the draw_every parameter, the program displays the current Buddhabrot image. (For example, you can use this to display the fractal every 1000 hits.)

The following code shows the DrawPoint method.

// Plot one point.
private void DrawPoint(double cx, double cy, int wid, int hgt,
    double dx , double dy , ref int max_hits,
    int[,] hits, int cutoff, ref int num_hits)
{
    const double ESCAPING = 4;

    // Zet Z0.
    double x, xx, y, yy;
    x = cx;
    y = cy;
    xx = x * x;
    yy = y * y;

    // Iterate.
    for (int i = 1; i <= cutoff; i++)
    {
        y = 2 * x * y + cy;
        x = xx - yy + cx;
        xx = x * x;
        yy = y * y;
        if (xx + yy >= ESCAPING) break;
    }

    // See if we escaped.
    if (xx + yy >= ESCAPING)
    {
        // Plot.
        x = cx;
        y = cy;
        xx = x * x;
        yy = y * y;

        // Iterate.
        for (int i = 1; i <= cutoff; i++)
        {
            int ix = (int)Math.Round((x - Wxmin) / dx);
            int iy = (int)Math.Round((y - Wymin) / dy);
            if ((ix >= 0) && (ix < hgt) & (iy >= 0) & (iy < wid))
            {
                hits[iy, ix] += 1;
                if (max_hits < hits[iy, ix]) max_hits = hits[iy, ix];
            }
            else
            {
                break;
            }

            y = 2 * x * y + cy;
            x = xx - yy + cx;
            xx = x * x;
            yy = y * y;
            if (xx + yy >= ESCAPING) break;
        }

        num_hits += 1;
    }
}

This method plots a single point. It initializes variables to represent a complex number Z0 and iterates the equation Z = Z2 + C up to cutoff times. If the points started heading towards infinity within those iterations, the code plots the point. To do that, it repeats the iteration. This time it increments a hit count for any point Zi that it generates during the iteration.

Periodically the program calls the following DisplayBrot method to display the current fractal.

// Draw the current image.
private void DisplayBrot(int wid, int hgt,
    int max_r, int max_g, int max_b,
    int[,] hit_r, int[,] hit_g, int[,] hit_b)
{
    using (Graphics gr = Graphics.FromImage(BrotBitmap))
    {
        gr.Clear(Color.Black);
    }

    double scale_r = 255 * 2.5 / max_r;
    double scale_g = 255 * 2.5 / max_g;
    double scale_b = 255 * 2.5 / max_b;

    for (int y = 0; y < hgt; y++)
    {
        for (int x = 0; x < wid; x++)
        {
            int r = (int)Math.Round(hit_r[x, y] * scale_r);
            if (r > 255) r = 255;
            int g = (int)Math.Round(hit_g[x, y] * scale_g);
            if (g > 255) g = 255;
            int b = (int)Math.Round(hit_b[x, y] * scale_b);
            if (b > 255) b = 255;

            BrotBitmap.SetPixel(x, y, Color.FromArgb(255, r, g, b));
        }
    }

    picCanvas.Refresh();
}

This method creates a Graphics object and uses it to clear the bitmap. It then examines each pixel's hit count and uses it to set the pixel's color. (You could use faster bitmap manipulation methods described in other posts to speed this up a bit, but that's not the slowest part of the program anyway, so I won't bother for this example. You can make the program run faster by increasing the Draw Every parameter.)






This entry was posted in algorithms, fractals, graphics, mathematics. Bookmark the permalink.

Leave a Reply

Your email address will not be published. Required fields are marked *