Die Community zu .NET und Classic VB.
Menü

Komplexe Zahlen - Das Apfelmännchen

 von 

Übersicht 

Dieses Tutorial ist eine Einführung in die Mathematik der komplexen Zahlen. Im Anschluss an etwas Theorie dazu wird in C# und VB.NET eine Klasse zur Darstellung einer komplexen Zahl implementiert. Für deren Demonstration wird anschliessend ein Programm gezeigt, welches mit Hilfe der komplexen Zahlen die Mandelbrotmenge, das sog. Apfelmännchen, darstellt.

Komplexe Zahlen  

Eine komplexe Zahl wird durch zwei reelle Zahlen beschrieben, Realteil und Imaginärteil. Der Imaginärteil ist durch die Multiplikation mit der imaginären Einheit Latex: i gekennzeichnet. Die Imaginäre Einheit ist als die Quadratwurzel von -1 definiert (Latex: i=sqrt(-1)). Demzufolge ist Latex: i^2=-1, was bei den späteren Berechnungen eine wichtige Rolle spielt.

Beispiel:
Latex: 2+5\cdot i
Latex: 2: Realteil (Re)
Latex: 5\cdot i: Imaginärteil (Im)

Rechnen mit komplexen Zahlen

Da wir nun den Aufbau von komplexen Zahlen kennen ist das Rechnen damit nicht mehr so komplex, wie man jetzt vielleicht vermuten mag. (Im Folgenden ist x der Realteil und y der Imaginärteil einer komplexen Zahl)

Anmerkung der Redaktion: In der Technik wird als imaginäre Einheit sehr häufig nicht der Buchstabe i, sondern j verwendet, um eine Verwechslung mit der Stromstärke zu vermeiden.

Addition

Bei der Addition werden der Realteil von Zahl 1 und der Realteil von Zahl 2 addiert. Gleiches gilt für die Imaginärteile:

Latex: z=z_1+z_2=(x_1+y_1\cdot i)+(x_2+y_2\cdot i)=x_1+x_2+(y_1+y_2)\cdot i

Beispiel:
Latex: z_1=5+10i \qquad z_2=-3+i=-3+1i
Latex: z=z_1+z_2=(5+10i)+(-3+1i)=5+(-3)+10i+1i
Latex: z=2+11i

Subtraktion

Die Subtraktion funktioniert ähnlich wie die Addition, nur dass eben subtrahiert wird:

Latex: z=z_1-z_2=(x_1+y_1\cdot i)-(x_2+y_2\cdot i)=x_1-x_2+(y_1-y_2)\cdot i

Beispiel:
Latex: z_1=5+10i \qquad z_2=-3+i=-3+1i
Latex: z=z_1-z_2=(5+10i)-(-3+1i)=5-(-3)+10i-1i
Latex: z=8+9i

Multiplikation

Jetzt wird es schon etwas komplexer, da nach den mathematischen Regeln ausgeklammert werden muss:

Latex: z=z_1\cdot z_2=(x_1+y_1\cdot i)\cdot (x_2+y_2\cdot i)=x_1\cdot x_2+x_1\cdot y_2\cdot i+x_2\cdot y_1\cdot i+y_1\cdot y_2\cdot i^2

Da hierbei der Term Latex: i^2 entsteht, welcher gemäss Definition -1 ergibt, wird nur die reelle Zahl Latex: y_1\cdot y_2 verwendet und negiert:

Latex: z=z_1\cdot z_2=x_1\cdot x_2-y_1\cdot y_2+(x_1\cdot y_2+x_2\cdot y_1)\cdot i

Beispiel:
Latex: z_1=5+10i \qquad z_2=-3+i=-3+1i
Latex: z=z_1\cdot z_2=(5+10i)\cdot (-3+1i)=5\cdot (-3)+5\cdot 1i+10\cdot i\cdot 1i
Latex: z=5\cdot (-3)+5\cdot 1i+10i\cdot (-3)-10\cdot 1=-15+5i-30i-10
Latex: z=-25-25i

Division

Die Division ist die schwierigste Grundrechenoperation bei den komplexen Zahlen. In einem ersten Schritt muss die imaginäre Einheit im Nenner (unter dem Bruchstrich) entfernt werden, was durch Erweiterung mit dem komplex-konjugierten Nenner möglich ist. Eine komplex-konjugierte Zahl entsteht, wenn das Vorzeichen des Imaginärteils umgedreht wird:
Latex: z=x+y\cdot i \qquad \overline{z}=x-y\cdot i
Bei dieser Erweiterung entsteht im Nenner folgender binomischer Term:
Latex: (x+y\cdot i)\cdot (x-y\cdot i)=x^2-y^2\cdot i^2=x^2+y^2
Damit ergibt sich für die Division:

Latex: z=\frac{z_1}{z_2}=\frac{x_1+y_1\cdot i}{x_2+y_2\cdot i}=\frac{(x_1+y_1\cdot i)\cdot (x_2-y_2\cdot i)}{(x_2+y_2\cdot i)\cdot (x_2-y_2\cdot i)}
Latex: z=\frac{x_1\cdot x_2-x_1\cdot y_2\cdot i+x_2\cdot y_1\cdot i+y_1\cdot y_2}{x_2^2+y_2^2}=\frac{x_1\cdot x_2+y_1\cdot y_2}{x_2^2+y_2^2}+\frac{x_2\cdot y_1-x_1\cdot y_2}{x_2^2+y_2^2}\cdot i

Beispiel:
Latex: z_1=5+10i \qquad z_2=-3+i=-3+1i
Latex: z=\frac{z_1}{z_2}=\frac{5+10i}{(-3)+1i}=\frac{(5+10i)\cdot ((-3)-1i)}{((-3)+1i)\cdot ((-3)-1i)}=\frac{5\cdot (-3)-5\cdot 1i+10i\cdot (-3)-10i\cdot 1i}{(-3)^2+1^2}
Latex: z=\frac{5\cdot(-3)+10\cdot 1}{(-3)^2+1^2}-\frac{5\cdot 1-10\cdot (-3)}{(-3)^2+1^2}\cdot i=\frac{-15+10}{9+1}-\frac{5+30}{9+1}\cdot i
Latex: z=-\frac{5}{10}-\frac{35}{10}i=-\frac{1}{2}-\frac{35}{10}i=-0.5-3.5i

Darstellung einer komplexen Zahl

Reelle Zahlen können noch auf einem Zahlenstrahl dargestellt werden. Doch was machen wir mit den komplexen Zahlen? Ganz einfach, wir tragen sie in ein Koordinatensystem (die gaußsche Zahlenebene) ein. Dabei ergibt der Realteil die x-Koordinate und der Imaginärteil die y-Koordinate.

Daraus ergibt sich eine weitere Schreibweise für komplexe Zahlen. Bei dieser wird das Argument (der Winkel Latex: \phi zur Realachse) und der Betrag r der Zahl angegeben. Der Betrag errechnet sich wie folgt:
Latex: r=\sqrt{Re^2+Im^2}
wobei Re der Realteil und Im der Imaginärteil der Zahl sind. Das Argument wird wie folgt berechnet:
Latex: \phi=\tan^{-1}\left(\frac{Im}{Re}\right)


Abbildung 1: Gaußsche Zahlenebene: z=5+2i

Nun zum Programmieren

Da wir uns nun durch die ganze Theorie gekämpft haben, beginnen wir jetzt mit dem Programmieren. Im Folgenden wird eine Klasse ComplexNumber erstellt, welche eine komplexe Zahl beschreibt.

Anmerkung der Redaktion: Die C#-Ausführung der folgenden Codeausschnitte wurde jeweils etwas gekürzt. Die volle Version kann am Ende des Artikels als Beispielprojekt heruntergeladen werden.

VB.NET

Public Class ComplexNumber
    Private mReal As Double
    Private mImag As Double

    Public Overrides Function ToString() As String
        Return "z = " & mReal.ToString() & " + " & mImag.ToString() & "i"
    End Function

    Public Sub New(ByVal Real As Double, ByVal Imag As Double)
        mReal = Real
        mImag = Imag
    End Sub

    Public Shared Function Parse(ByVal Number As String) As ComplexNumber
     'Number muss etwa so aussehen: "5,59+56i" oder "5,59+56*i"

        Number = Number.Replace(" ", "")
        Number = Number.Replace("*", "")
        Number = Number.Replace(".", ",")

        If Number(Number.Length - 1) = "i"c Then
            Return New ComplexNumber(Double.Parse(Number.Substring(0, _
                Number.IndexOf("+"))), _
                Double.Parse(Number.Substring(Number.IndexOf("+") + 2)))
        Else
            Return Nothing
        End If
    End Function

    Public Property RealPart() As Double
        Get
            Return mReal
        End Get
        Set(ByVal value As Double)
            mReal = value
        End Set
    End Property

    Public Property ImaginaerPart() As Double
        Get
            Return mImag
        End Get
        Set(ByVal value As Double)
            mImag = value
        End Set
    End Property

    Public Function GetBetrag() As Double
        Return Math.Sqrt(mReal * mReal + mImag * mImag)
    End Function

    Public Function GetArgument() As Double
        Dim tan As Double = Math.Atan(mImag / mReal) * 180 / Math.PI

        If mReal < 0 Then
            tan += 180
        End If
        If tan < 0 Then
            tan = 360 + tan
        End If

        Return tan
    End Function

#Region "operators"
    Public Shared Operator +(ByVal z0 As ComplexNumber, _
            ByVal z1 As ComplexNumber) As ComplexNumber
        Return New ComplexNumber(z0.mReal + z1.mReal, z0.mImag + z1.mImag)
    End Operator

    Public Shared Operator -(ByVal z0 As ComplexNumber, _
            ByVal z1 As ComplexNumber) As ComplexNumber
        Return New ComplexNumber(z0.mReal - z1.mReal, z0.mImag - z1.mImag)
    End Operator

    Public Shared Operator *(ByVal z0 As ComplexNumber, _ 
            ByVal z1 As ComplexNumber) As ComplexNumber
        Return New ComplexNumber(z0.mReal * z1.mReal - z0.mImag * z1.mImag, _
                                 z0.mReal * z1.mImag + z1.mReal * z0.mImag)
    End Operator

    Public Shared Operator ^(ByVal z0 As ComplexNumber, _
            ByVal ex As Integer) As ComplexNumber
        Dim ret As ComplexNumber = z0

        For i As Integer = 1 To ex - 1
            ret *= z0
        Next

        Return ret
    End Operator

    Public Shared Operator /(ByVal z0 As ComplexNumber, _
            ByVal z1 As ComplexNumber) As ComplexNumber
        Dim real As Double = (z0.mReal * z1.mReal + z0.mImag * z1.mImag) / _    
                             (z1.mReal * z1.mReal + z1.mImag * z1.mImag)
        Dim imag As Double = (z0.mImag * z1.mReal - z0.mReal * z1.mImag) / _
                             (z1.mReal * z1.mReal + z1.mImag * z1.mImag)

        Return New ComplexNumber(real, imag)
    End Operator
#End Region
End Class

Listing 1: ComplexNumber in VB.NET

C#

              
public class ComplexNumber
{
    private double mReal;
    private double mImag;

    public override string ToString()
    {
        return "z = " + mReal.ToString() + " + " + mImag.ToString() + "i";
    }

    public ComplexNumber(double Real, double Imag)
    {
        mReal = Real;
        mImag = Imag;
    }

    public static ComplexNumber Parse(string Number)
    {
         //Number muss etwa so aussehen: "5,59+56i" oder "5,59+56*i"

        Number = Number.Replace(" ", "");
        Number = Number.Replace("*", "");
        Number = Number.Replace(".", ",");

        if (Number[Number.Length - 1] == 'i')
        {
            return new ComplexNumber(
                double.Parse(Number.Substring(0, Number.IndexOf("+"))), 
                double.Parse(Number.Substring(Number.IndexOf("+") + 2)));
        }
        else
            return null;
    }

    public double RealPart
    {
        get { return mReal; }
        set { mReal = value; }
    }

    public double ImaginaerPart
    {
        get { return mImag; }
        set { mImag = value; }
    }

    public double GetBetrag()
    {
        return Math.Sqrt(mReal * mReal + mImag * mImag);
    }

    public double GetArgument()
    {
        double tan = Math.Atan(mImag / mReal) * 180 / Math.PI;

        if (mReal < 0) tan += 180;
        if (tan < 0) tan = 360 + tan;

        return tan;
    }

    #region operators
    public static ComplexNumber operator +(ComplexNumber z0, ComplexNumber z1)
    {
        return new ComplexNumber(z0.mReal + z1.mReal, z0.mImag + z1.mImag);
    }

    public static ComplexNumber operator -(ComplexNumber z0, ComplexNumber z1)
    {
        return new ComplexNumber(z0.mReal - z1.mReal, z0.mImag - z1.mImag);
    }

    public static ComplexNumber operator *(ComplexNumber z0, ComplexNumber z1)
    {
        return new ComplexNumber(z0.mReal * z1.mReal - z0.mImag * z1.mImag, 
                                 z0.mReal * z1.mImag + z1.mReal * z0.mImag);
    }

    public static ComplexNumber operator ^(ComplexNumber z0, int ex)
    {
        ComplexNumber ret = z0;

        for (int i = 1; i < ex; i++)
            ret *= z0;

        return ret;
    }

    public static ComplexNumber operator /(ComplexNumber z0, ComplexNumber z1)
    {
        double real = (z0.mReal * z1.mReal + z0.mImag * z1.mImag) / 
                      (z1.mReal * z1.mReal + z1.mImag * z1.mImag);
        double imag = (z0.mImag * z1.mReal - z0.mReal * z1.mImag) / 
                      (z1.mReal * z1.mReal + z1.mImag * z1.mImag);

        return new ComplexNumber(real, imag);
    }
    #endregion
}
              

Listing 2: ComplexNumber in C#

Das Apfelmännchen  

Wir wissen nun einiges über komplexe Zahlen, also wenden wir uns nun einer Anwendung, dem Apfelmännchen, zu. Das Apfelmännchen trägt den Namen, da es in der Grundform wie ein Apfel aussieht. Der eigentliche Name, Mandelbrotmenge, kommt vom Entdecker Benoît Mandelbrot. Die Mandelbrot Menge ist ein Fraktal, also eine Punktmenge, die eine gewisse Selbstähnlichkeit aufweist und auf beliebig großer Vergrößerung immer noch eine Struktur aufweist. Trotz dessen ist das Zeichnen der Mandelbrot Menge am Computer nicht besonders schwierig, sondern eher etwas rechenaufwändig.

Der Sichtbereich

Bevor wir zeichnen können müssen wir den Bereich festlegen, den wir zeichnen wollen. Um das Apfelmännchen komplett sehen zu können müssen wir den Ausschnitt so wählen, dass in der oberen linken Ecke unserer Zeichenebene der Realteil x=-2 und der Imaginärteil y=-1 beträgt. In der unteren rechten Ecke muss der Realteil x=1 und der Imaginärteil y=1 betragen. Wenn wir ein Rechteck betrachten hat dieses also Den x-Wert -2, den y-Wert -1, die Höhe 2 und die Breite 3. Da wir in unserem Programm natürlich auch zoomen wollen, müssen wir uns vorher eine neue Struktur erstellen, um mit Doubles arbeiten zu können:

Public Structure RectangleD
    Private mWidth As Double
    Private mHeight As Double
    Private mX As Double
    Private mY As Double

    Public Sub New(ByVal Width As Double, _
                   ByVal Height As Double, _
                   ByVal X As Double, _
                   ByVal Y As Double)
        mWidth = Width
        mHeight = Height
        mX = X
        mY = Y
    End Sub

    Public Property Y() As Double
        Get
            Return mY
        End Get
        Set(ByVal value As Double)
            mY = value
        End Set
    End Property

    Public Property X() As Double
        Get
            Return mX
        End Get
        Set(ByVal value As Double)
            mX = value
        End Set
    End Property

    Public Property Width() As Double
        Get
            Return mWidth
        End Get
        Set(ByVal value As Double)
            mWidth = value
        End Set
    End Property

    Public Property Height() As Double
        Get
            Return mHeight
        End Get
        Set(ByVal value As Double)
            mHeight = value
        End Set
    End Property
End Structure

Public Structure PointD
    Private mx As Double
    Private my As Double

    Public Property x() As Double
        Get
            Return mx
        End Get
        Set(ByVal value As Double)
            mx = value
        End Set
    End Property

    Public Property y() As Double
        Get
            Return my
        End Get
        Set(ByVal value As Double)
            my = value
        End Set
    End Property

    Public Sub New(ByVal x As Double, ByVal y As Double)
        mx = x
        my = y
    End Sub
End Structure

Listing 3: Rechteck- und Punktstruktur in VB.NET

            
public struct RectangleD
{
      private double mWidth;
      private double mHeight;
      private double mX;
      private double mY;

      public RectangleD(double Width, 
                        double Height, 
                        double X, 
                        double Y)
      {
          mWidth = Width;
          mHeight = Height;
          mX = X;
          mY = Y;
      }

      public double Y
      {
          get { return mY; }
          set { mY = value; }
      }

      public double X
      {
          get { return mX; }
          set { mX = value; }
      }

      public double Width
      {
          get { return mWidth; }
          set { mWidth = value; }
      }

      public double Height
      {
          get { return mHeight; }
          set { mHeight = value; }
      }
}
            

Listing 4: Rechteckstruktur in C#

Die Mathematik des Apfelmännchens

Um das Apfelmännchen zu zeichnen muss man erst einmal seinen mathematischen Hintergrund kennen. Das Apfelmännchen ist eine Menge von Punkten auf der gaußschen Ebene, die meistens schwarz dargestellt werden. Das Fraktal wird über die Rekursionsgleichung Latex: z_n=z_{n-1}^2+c berechnet, wobei c der Wert des Punktes in der gaußschen Ebene (komplexe Zahl) ist und Latex: z_0=0. Dabei entstehen entweder Komplexe Zahlen, die immer annähernd gleich bleiben oder nach kurzer Zeit gegen unendlich streben. Es ist mathematisch bewiesen, dass eine Komplexe Zahl, deren Betrag größer als zwei ist, beim Potenzieren gegen unendlich strebt, also wird die Rekursion dann abgebrochen, wenn Latex: |z_n|&gt;2. Bei den anderen Zahlen, die später die Mandelbrotmenge darstellen, würde n bis unendlich gehen, da deren Betrag nie über 2 geht. Da wir am Computer jedoch keine Schleife bis unendlich laufen lassen können müssen wir etwas schummeln, indem wir nach einem bestimmten Wert abbrechen und den getesteten Punkt als zur Menge zugehörig zählen. Die Farben außerhalb der Menge entstehen durch das Einfärben des Pixels mit der Farbe, die einer bestimmten Anzahl von Iterationen (Rekursionsschritte, also n) zugeordnet ist.


Abbildung 2: Mandelbrotmenge mit eingefärbter Umgebung

Die Programmierung

Bevor wir nun zur Hauptklasse kommen, müssen wir noch eine Struktur erstellen: SizeD, für die Angabe einer Größe mit doppelter Genauigkeit:

Public Structure SizeD
    Private mWidth As Double
    Private mHeight As Double

    Public Sub New(ByVal Width As Double, _
                   ByVal Height As Double)
        mWidth = Width
        mHeight = Height
    End Sub

    Public Property Width() As Double
        Get
            Return mWidth
        End Get
        Set(ByVal value As Double)
            mWidth = value
        End Set
    End Property

    Public Property Height() As Double
        Get
            Return mHeight
        End Get
        Set(ByVal value As Double)
            mHeight = value
        End Set
    End Property
End Structure

Listing 5: SizeD in VB.NET

            
public struct SizeD
{
    private double mWidth;
    private double mHeight;

    public SizeD(double Width, double Height)
    {
        mWidth = Width;
        mHeight = Height;
    }

    public double Width
    {
        get { return mWidth; }
        set { mWidth = value; }
    }

    public double Height
    {
        get { return mHeight; }
        set { mHeight = value; }
    }
}
            

Listing 6: SizeD in C#

Nun zur Hauptklasse ClassMandel:

Klassenvariablen

Imports System.Drawing.Imaging
Imports System.Runtime.InteropServices
Imports System.IO

Public Class ClassMandel

    Private mImageSize As New Size(640, 480) 'Größe des Bildes, das gerendert wird
    Private mPosition As New RectangleD(4, 3, -2.5, -1.5) 'zu rendernder Ausschnitt des Fraktals
    Private mStartVal As New ComplexNumber(0, 0) 'z_0
    Private mPal As ColorPalette = New Bitmap(1, 1, PixelFormat.Format8bppIndexed).Palette 'Palette (Später verwendet für Farbrotation)
    Private mProg As Double = 0 'Renderfortschritt in Prozent
    Private mPower As Integer = 2 'Exponent von z in der Rekursionsgleichung
    Private mIterations As Integer = 300 'Maximal zu durchlaufende Iterationen
    
    '...
    
End Class

Listing 7: Klassenvariablen in VB.NET

private Size mImageSize = new Size(640, 480);
private RectangleD mPosition = new RectangleD(4, 3, -2.5, -1.5); 
private ComplexNumber mStartVal = new ComplexNumber(0,0);
private ColorPalette mPal = new Bitmap(1, 1, 
        PixelFormat.Format8bppIndexed).Palette;
private double mProg = 0;
private int mPower = 2;
private int mIterations = 300;

Listing 8: Klassenvariablen in C#

Properties

'...
          
Public Property Palette() As ColorPalette
    Get
        Return mPal
    End Get
    Set(ByVal value As ColorPalette)
        mPal = value
    End Set
End Property

Public Property StartPos() As ComplexNumber
    Get
        Return mStartVal
    End Get
    Set(ByVal value As ComplexNumber)
        mStartVal = value
    End Set
End Property

Public Property Power() As Integer
    Get
        Return mPower
    End Get
    Set(ByVal value As Integer)
        mPower = value
    End Set
End Property

Public Property ViewRect() As RectangleD
    Get
        Return mPosition
    End Get
    Set(ByVal value As RectangleD)
        mPosition = value
    End Set
End Property

Public Property ImageSize() As Size
    Get
        Return mImageSize
    End Get
    Set(ByVal value As Size)
        mImageSize = value
    End Set
End Property

Public Property Iterations() As Integer
    Get
        Return mIterations
    End Get
    Set(ByVal value As Integer)
        mIterations = value
    End Set
End Property

Public ReadOnly Property Progress() As Double
    Get
        Return mProg
    End Get
End Property

'...

Listing 9: Properties in VB.NET

              
public ColorPalette Palette
{
    get { return mPal; }
    set { mPal = value; }
}

public ComplexNumber StartPos
{
    get { return mStartVal; }
    set { mStartVal = value; }
}

public int Power
{
    get { return mPower; }
    set { mPower = value; }
}

public RectangleD ViewRect
{
    get { return mPosition; }
    set { mPosition = value; }
}

public Size ImageSize
{
    get { return mImageSize; }
    set { mImageSize = value; }
}

public int Iterations
{
    get { return mIterations; }
    set { mIterations = value; }
}

public double Progress
{
    get { return mProg; }
}
              

Listing 10: Properties in C#

Hilfsroutinen

'...

Private Function Recur(ByVal z As ComplexNumber, ByVal c As ComplexNumber) As ComplexNumber
    'Hier ist nur die Rekursionsgleichung extra (für etwaige veränderungen)
    Return (z ^ mPower) + c '([pixel] ^ pow + start)
End Function

'                         Startwert für z            | Zweitmember der Rekursionsgleichung
Private Function DoRecur(ByVal Start As ComplexNumber, ByVal c As ComplexNumber) As Integer
    
    'Diese Routine führt die Rekursion durch
    Dim n As Integer = 0
    Dim z As ComplexNumber = Start

    While n < mIterations AndAlso Not (z.ImaginaerPart * z.ImaginaerPart + z.RealPart * z.RealPart > 4)
        'Wurzelziehen ist langsam
        n += 1
        z = Recur(z, c)
    End While

    Return n
End Function

Private Function GetStep() As SizeD
    'schrittgröße im Koordinatensystem errechnen, damit jedes Pixel richtig berechnet wird
    Return New SizeD(mPosition.Width / mImageSize.Width, mPosition.Height / mImageSize.Height)
End Function

Public Function GetPalFile(ByVal FileName As String) As ColorPalette
    'Einlesen eines *.pal Files (Aufbau: 256*4 Bytes = 256 Farben)
    Dim bmp As New Bitmap(1, 1, PixelFormat.Format8bppIndexed)
    Dim pal As ColorPalette = bmp.Palette
    'Palette mit 256 Einträgen übernehmen
    Dim stream As New FileStream(FileName, FileMode.Open)

    For i As Integer = 0 To pal.Entries.Length - 1
        pal.Entries(i) = Color.FromArgb(stream.ReadByte(), stream.ReadByte(), _
                                        stream.ReadByte(), stream.ReadByte())
    Next

    stream.Close()

    Return pal
End Function

'Punkt, an dem die maus geklickt wurde        | Punkt, an dem die Maus losgelassen wurde
Public Function GetNewRect(ByVal Down As Point, ByVal Up As Point) As RectangleD
    
    'Berechnet den neuen Ausschnitt im Koordinatensystem für punkte auf der Zeichenfläche (Punkte in Pixel)
    Dim Steps As SizeD = GetStep()
    Dim DownP As New PointD(mPosition.X + (Steps.Width * Down.X), mPosition.Y + (Steps.Height * Down.Y))
    Dim UpP As New PointD(mPosition.X + (Steps.Width * Up.X), mPosition.Y + (Steps.Height * Up.Y))
    Dim minX As Double = Math.Min(DownP.X, UpP.X)
    Dim minY As Double = Math.Min(DownP.Y, UpP.Y)
    Dim maxX As Double = Math.Max(DownP.X, UpP.X)
    Dim maxY As Double = Math.Max(DownP.Y, UpP.Y)

    Return New RectangleD(maxX - minX, maxY - minY, minX, minY)
End Function

'...

Listing 11: Hilfsroutinen in VB.NET

              
private ComplexNumber Recur(ComplexNumber z, ComplexNumber c)
{//Hier ist nur die Rekursionsgleichung extra (für etwaige veränderungen)
    return (z ^ mPower) + c; //([pixel] ^ pow + start)
}

private int DoRecur(ComplexNumber Start/*Startwert für z*/, ComplexNumber c/*Zweitmember der Rekursionsgleichung*/)
{//Diese Routine führt die Rekursion durch
    int n = 0;
    ComplexNumber z = Start;

    while (n < mIterations && !(z.ImaginaerPart * z.ImaginaerPart + z.RealPart * z.RealPart > 4)) //Wurzelziehen ist langsam
    {
        n++;
        z = Recur(z, c);
    }

    return n;
}

private SizeD GetStep()
{//schrittgröße im Koordinatensystem errechnen, damit jedes Pixel richtig berechnet wird
    return new SizeD(mPosition.Width / mImageSize.Width, mPosition.Height / mImageSize.Height);
}

public ColorPalette GetPalFile(string FileName)
{//Einlesen eines *.pal Files (Aufbau: 256*4 Bytes = 256 Farben)
    Bitmap bmp = new Bitmap(1, 1, PixelFormat.Format8bppIndexed);
    ColorPalette pal = bmp.Palette;//Palette mit 256 Einträgen übernehmen
    FileStream stream = new FileStream(FileName, FileMode.Open);

    for (int i = 0; i < pal.Entries.Length; i++)
    {
        pal.Entries[i] = Color.FromArgb(stream.ReadByte(), stream.ReadByte(), stream.ReadByte(), stream.ReadByte());
    }

    stream.Close();

    return pal;
}

public RectangleD GetNewRect(Point Down/*Punkt, an dem die maus geklickt wurde*/, Point Up/*Punkt, an dem die Maus losgelassen wurde*/)
{//Berechnet den neuen Ausschnitt im Koordinatensystem für punkte auf der Zeichenfläche (Punkte in Pixel)
    SizeD Steps = GetStep();
    PointD DownP = new PointD(mPosition.X + (Steps.Width * Down.X), mPosition.Y + (Steps.Height * Down.Y));
    PointD UpP = new PointD(mPosition.X + (Steps.Width * Up.X), mPosition.Y + (Steps.Height * Up.Y));
    double minX = Math.Min(DownP.X, UpP.X);
    double minY = Math.Min(DownP.Y, UpP.Y);
    double maxX = Math.Max(DownP.X, UpP.X);
    double maxY = Math.Max(DownP.Y, UpP.Y);

    return new RectangleD(maxX - minX, maxY - minY, minX, minY);
}
              

Listing 12: Hilfsroutinen in C#

Hauptroutine

'...

Public Function DrawMandelbrot() As Bitmap
    'Zeichnet die Mandelbrotmenge mit den voreingestellten Parametern
    Dim bmp As New Bitmap(mImageSize.Width, mImageSize.Height, PixelFormat.Format8bppIndexed)

    bmp.Palette = mPal

    Dim Steps As SizeD = GetStep()
    Dim NColorP As Double
    Dim tmp As Integer

    Dim data0 As BitmapData = bmp.LockBits(New Rectangle(Point.Empty, mImageSize), ImageLockMode.ReadWrite, PixelFormat.Format8bppIndexed)
    Dim ColorData As Byte() = New Byte(data0.Stride * mImageSize.Height - 1) {}
    Marshal.Copy(data0.Scan0, ColorData, 0, ColorData.Length)

    For x As Integer = 0 To mImageSize.Width - 1
        'Jedes Pixel durchlaufen
        For y As Integer = 0 To mImageSize.Height - 1
            tmp = DoRecur(mStartVal, New ComplexNumber(mPosition.X + (Steps.Width * x), mPosition.Y + (Steps.Height * y)))
            'Rekursion durchführen
            NColorP = (tmp * 1.0) / mIterations

            Dim s As Byte = CByte(Math.Truncate(NColorP * 255))

            'Einfärben TRICK: da 8Bit Indexed Bitmap können verschiedene Farbpaletten geladen werden)
            ColorData(x + y * data0.Stride) = s
        Next

        mProg = (x * 1.0) / mImageSize.Width
        Application.DoEvents()
    Next

    Marshal.Copy(ColorData, 0, data0.Scan0, ColorData.Length)
    bmp.UnlockBits(data0)

    Return bmp
End Function

Listing 13: Hauptroutine in VB.NET

              
public Bitmap DrawMandelbrot()
{//Zeichnet die Mandelbrotmenge mitden voreingestellten Parametern
    Bitmap bmp = new Bitmap(mImageSize.Width, mImageSize.Height, PixelFormat.Format8bppIndexed);

    bmp.Palette = mPal;

    SizeD Steps = GetStep();
    double NColorP;
    int tmp;

    BitmapData data0 = bmp.LockBits(new Rectangle(Point.Empty, mImageSize), ImageLockMode.ReadWrite, PixelFormat.Format8bppIndexed);
    byte[] ColorData = new byte[data0.Stride * mImageSize.Height];
    Marshal.Copy(data0.Scan0, ColorData, 0, ColorData.Length);

    for (int x = 0; x < mImageSize.Width; x++)
    {//Jedes Pixel durchlaufen
        for (int y = 0; y < mImageSize.Height; y++)
        {
            tmp = DoRecur(mStartVal,  new ComplexNumber(mPosition.X + (Steps.Width * x), mPosition.Y + (Steps.Height * y)));//Rekursion durchführen
            NColorP = (tmp * 1D) / mIterations;

            byte s = (byte)(NColorP * 255);

            ColorData[x + y * data0.Stride] = s;//Einfärben 
            //(TRICK: da 8Bit Indexed Bitmap können verschiedene Farbpaletten geladen werden)
        }

        mProg = (x * 1D) / mImageSize.Width;
        Application.DoEvents();
    }

    Marshal.Copy(ColorData, 0, data0.Scan0, ColorData.Length);
    bmp.UnlockBits(data0);

    return bmp;
}
              

Listing 14: Hauptroutine in C#

Genutzte Namensbereiche

  • System
  • System.Collections.Generic
  • System.Linq
  • System.Text
  • System.Drawing
  • System.Windows.Forms
  • System.Drawing.Imaging
  • System.Runtime.InteropServices
  • System.IO

Beispielprogramm  

Das Beispielprogramm ist in C# geschrieben, ist aber sehr einfach gehalten und lässt sich ohne viel Mühe nach VB.Net übersetzen.

Zoomen kann man über Drag & Drop auf der Zeichenfläche.

Wer sich Zeit nimmt und ein Stückchen zoomt, der wird feststellen, dass nach einer bestimmten Tiefe die Grafik wie verpixelt wirkt. Das kommt durch die "Ungenauigkeit" vom Zahlentyp Double. Also wenn sich jemand von den Assemblerprogrammierern für so etwas interessiert und etwas Zeit hat, kann er ja mal einen Zahlentyp schreiben, der etwas genauer ist als double (vielleicht mit 50 Nachkommastellen ohne Exponentialschreibweise). Ich wäre dann auch daran interessiert :-)

Screenshot


Abbildung 3: Screenshot des Beispielprogramms

Download des Beispielprogramms

Beispielprogramm in C# 

Ihre Meinung  

Falls Sie Fragen zu diesem Tutorial haben oder Ihre Erfahrung mit anderen Nutzern austauschen möchten, dann teilen Sie uns diese bitte in einem der unten vorhandenen Themen oder über einen neuen Beitrag mit. Hierzu können sie einfach einen Beitrag in einem zum Thema passenden Forum anlegen, welcher automatisch mit dieser Seite verknüpft wird.