Rendera mandelbrotmängden

Min första kontakt med mandelbrotmängden var ett BASIC-program av Bo E. Carlsson runt 1990. Sedan dess har Andrej Bauer skrivit ett C++-program som skriver mandelbrotmängden till en bildfil. Programmet gick att kompilera med en C#-kompilator med endast små justeringar, vilket syns här:

var rawData = new ImageRawData(width, height);


var xmin = 0.27085;
var xmax = 0.27100;
var ymin = 0.004640;
var ymax = 0.004810;
var maxiter = 1000;

var dx = (xmax - xmin) / width;
var dy = (ymax - ymin) / height;

for (var j = 0; j < height; j++)
{
    var y = ymax - j * dy;
    for (var i = 0; i < width; i++)
    {
        var u = 0.0;
        var v = 0.0;
        var u2 = u * u;
        var v2 = v * v;
        var x = xmin + i * dx;
        
        int k;
        for (k = 1; k < maxiter && u2 + v2 < 4.0; k++)
        {
            v = 2 * u * v + y;
            u = u2 - v2 + x;
            u2 = u * u;
            v2 = v * v;
        }

        rawData.SetValue(i, j, k >= maxiter ? null : k);
    }
}

Den anpassade koden varken ritar bilden eller skriver den fill någon fil, utan samlar resultatet i en 2-dimensionell array som heter rawData (av typen ImageRawData). Anledningen till det är att jag vill kunna lägga på renderingsfilter efter att fraktalen är beräknad. I implementation har jag hittills endast lagt in en linjär interpolering till en gråskala mellan 0 och 255, men möjligheten att hitta på något roligt finns där.

public class ImageRawData
{
    private readonly int _width;
    private readonly int _height;
    private readonly int?[,] _data;

    public ImageRawData(int width, int height)
    {
        _width = width;
        _height = height;
        _data = new int?[width, height];
    }

    public void SetValue(int x, int y, int? value) =>
        _data[x, y] = value;

    public int GetValue(int x, int y) =>
        _data[x, y] ?? 0;

    private int GetLargest()
    {
        var largest = int.MinValue;

        for (var y = 0; y < _height; y++)
            for (var x = 0; x < _width; x++)
                if (_data[x, y].HasValue && _data[x, y] > largest)
                    largest = _data[x, y]!.Value;

        return largest;
    }

    private int GetSmallest()
    {
        var smallest = int.MaxValue;

        for (var y = 0; y < _height; y++)
            for (var x = 0; x < _width; x++)
                if (_data[x, y].HasValue && _data[x, y] < smallest)
                    smallest = _data[x, y]!.Value;

        return smallest;
    }

    public void Interpolate(int min, int max)
    {
        var smallest = GetSmallest();
        var largest = GetLargest();

        for (var y = 0; y < _height; y++)
        {
            for (var x = 0; x < _width; x++)
            {
                if (_data[x, y].HasValue)
                {
                    var v1 = _data[x, y]!.Value;
                    var v2 = LinearInterpolate(v1, smallest, largest, min, max);
                    _data[x, y] = v2;
                    continue;
                }

                _data[x, y] = max;
            }
        }
    }

    private static int LinearInterpolate(int value, int smallest, int largest, int min, int max)
    {
        var result = min + (value - smallest) * (max - min) / (largest - smallest);
        
        if (result < min)
            result = min;

        if (result > max)
            result = max;

        return result;
    }
}

Själva renderingen har jag lagt i en iteration, där jag påverkar zoom-faktorn en aning, för att kunna skapa en animation. Jag gör det lite asymmetriskt för effektens skull. Iteratorn heter frameCount.

var xmin = 0.27085 + frameCount * 0.0000001;
var xmax = 0.27100 - frameCount * 0.0000002;
var ymin = 0.004640 + frameCount * 0.0000003;
var ymax = 0.004810 - frameCount * 0.0000004;
var maxiter = 1000;

Sen är det bara att kapsla in kalaset i ett ramverk. Jag använder Windows Forms så att jag kan titta på varje renderad bild medan animationen renderas. Eftersom själva resultatet är animationen som skrivs till disk, inte själva programmet, så har jag kostat på mig att göra allt arbete i GUI-tråden. Sedvanliga ursäkter.

using System.Drawing.Imaging;

namespace WinFormsApp1
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void Form1_Shown(object sender, EventArgs e)
        {
            Refresh();
            using var g = CreateGraphics();
            const int width = 1024;
            const int height = 1024;

            for (var frameCount = 0; frameCount < 1000; frameCount++)
            {
                var rawData = new ImageRawData(width, height);

                var xmin = 0.27085 + frameCount * 0.0000001;
                var xmax = 0.27100 - frameCount * 0.0000002;
                var ymin = 0.004640 + frameCount * 0.0000003;
                var ymax = 0.004810 - frameCount * 0.0000004;
                var maxiter = 1000;

                var dx = (xmax - xmin) / width;
                var dy = (ymax - ymin) / height;

                for (var j = 0; j < height; j++)
                {
                    var y = ymax - j * dy;
                    for (var i = 0; i < width; i++)
                    {
                        var u = 0.0;
                        var v = 0.0;
                        var u2 = u * u;
                        var v2 = v * v;
                        var x = xmin + i * dx;
                        
                        int k;
                        for (k = 1; k < maxiter && u2 + v2 < 4.0; k++)
                        {
                            v = 2 * u * v + y;
                            u = u2 - v2 + x;
                            u2 = u * u;
                            v2 = v * v;
                        }

                        rawData.SetValue(i, j, k >= maxiter ? null : k);
                    }
                }

                Application.DoEvents();
                rawData.Interpolate(0, 255);
                using var image = new Bitmap(width, height);

                for (var yy = 0; yy < height; yy++)
                    for (var xx = 0; xx < width; xx++)
                        image.SetPixel(xx, yy, Color.FromArgb(
                            rawData.GetValue(xx, yy),
                            rawData.GetValue(xx, yy),
                            rawData.GetValue(xx, yy)
                        ));

                g.DrawImage(image, 0, 0);
                Application.DoEvents();
                image.Save($@"C:\Temp\Frame{frameCount:00000}.png", ImageFormat.Png);
                Application.DoEvents();
            }
        }
    }
}

Resultatet för just dessa värden är följande video:

Leave a Reply

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