Zum Inhalt springen

Das Apfelmännchen/ Druckversion

Aus Wikibooks

Hauptteil[Bearbeiten]

Bildschirmaufnahme des Java-Programms FraktaleMengeAusgabe zur Berechnung von Julia-Mengen.

Dieses Wikibook beschäftigt sich mit der softwaretechnischen Erstellung von fraktalen Mengen mit der Hilfe von strukturierten Java-Programmen, insbesondere mit der Berechnung von Julia-Mengen und der Mandelbrot-Menge. Letztere wird wegen der Ähnlichkeit ihrer geometrischen Form zu einer Figur aus verschieden großen Äpfeln auch Apfelmännchen genannt.

Julia-Mengen[Bearbeiten]

Julia-Mengen sind nach dem französischen Mathematiker  Gaston Maurice Julia (* 1893; † 1978) benannt, der diese 1917 fast gleichzeitig mit dem ebenfalls französischen Mathematiker  Pierre Fatou (* 28. 1878; † 1929) beschrieben hatte.

Beispiel einer Julia-Menge mit dem Parameter c = (-0,5142|0,5142).
Zoomfahrt in eine Julia-Menge in der komplexwertigen z-Ebene mit der komplexwertigen Polynomfunktion zweiten Grades

und den Parametern
cre = cim = -0.5251993

Die Mandelbrot-Menge[Bearbeiten]

1979 begann der französisch-US-amerikanische Mathematiker  Benoît Mandelbrot (* 1924; † 2010) damit, an fraktalen Julia-Mengen weiterzuforschen. Mit Hilfe von Computern und Software konnte er bestimmte Eigenschaften von Julia-Mengen graphisch darstellen und entdeckte dabei die nach ihm benannte Mandelbrot-Menge.

Die Mandelbrot-Menge in der Darstellung als Apfelmännchen, bei der die negative reelle Achse der Zahlenebene nach oben zeigt.

Siehe auch Wikibook Komplexe Zahlen / Anwendung in der Mathematik / Mandelbrot-Menge

Komplexe Zahlen[Bearbeiten]

Komplexen Zahlenebene mit einer komplexen Zahl in kartesischen Koordinaten (a|b) und als Alternative auch in Polarkoordinaten (r|φ), wobei für den Abstand zum Ursprung (0|0) und für den entgegen dem Uhrzeigersinn gemessenen Winkel zur positiven reellen Achse stehen.

Komplexe Zahlen bestehen aus einem reellen Anteil und einem imaginären Anteil, die beide durch eine reelle Zahl repräsentiert werden können, die orthogonal in einer Zahlenebene dargestellt werden können. Hierbei wird in der Regel der reelle Anteil auf der horizontalen Achse und der imaginäre Anteil auf der vertikalen Achse aufgetragen. Jede komplexe Zahl hat auf diese Weise einen bestimmten Ort in der komplexen Zahlenebene, die nach dem deutschen Mathematiker  Carl Friedrich Gauß (* 1777; † 1857) auch Gaußsche Ebene genannt wird.

Die komplexe Zahl mit dem Zahlendupel (1|0) entspricht der reellen Einheitszahl , und die komplexe Zahl mit dem Zahlendupel (0|1) entspricht der imaginären Einheitszahl . Das Quadrat von hat den Wert , und das Quadrat von hat den Wert :

Eine beliebige komplexe Zahl kann zum Beispiel als Summe eines reellen Anteils und eines imaginären Anteils dargestellt werden, in dem die Zahl i mit dem imaginären Anteil multipliziert wird:

In einer Java-Klasse KomplexeZahl können diese beiden Attribute folgendermaßen programmiert werden:

public class KomplexeZahl 
{
	// Die beiden Instanzvariablen re und im sind die Datenfelder des Datentyps KomplexeZahl
	public double re; // Realteil der komplexen Zahl
	public double im; // Imaginaerteil der komplexen Zahl

	/*
	 * Konstruktor zum Initialisieren einer Instanz einer komplexen Zahl
	 * @param re: Realteil der komplexen Zahl
	 * @param im: Imaginaerteil der komplexen Zahl
	 */
	public KomplexeZahl (double re, double im)
	{
		this.re = re;
		this.im = im;
	}
}
Darstellung der Addition zweier komplexer Zahlen und in der komplexen Zahlenebene.

Die Summe zweier komplexen Zahlen und kann berechnet werden, indem die beiden reellen und die beiden imaginären Anteile jeweils addiert werden:

In Bezug auf die komplexen Zahlen

In Bezug auf die reellen Anteile der komplexen Zahlen:

In Bezug auf die imaginären Anteile der komplexen Zahlen:

In der Java-Methode addiereZu ist diese Rechenvorschrift implementiert:

	/*
	 * Addiert komplexerSummand zu komplexeZahl und speichert das Ergebnis in komplexeZahl
	 * @param komplexeZahl: komplexe Zahl, zu der addiert wird
	 * @param komplexerSummand: komplexe Zahl, die zu komplexeZahl addiert wird
	 */
	public static void addiereZu (KomplexeZahl komplexeZahl, KomplexeZahl komplexerSummand)
	{
		double reSumme = komplexeZahl.re + komplexerSummand.re;
		double imSumme = komplexeZahl.im + komplexerSummand.im;

		komplexeZahl.re = reSumme;
		komplexeZahl.im = imSumme;
	}

Das Quadrat einer komplexen Zahl kann mit der folgenden Vorschrift berechnet werden:

In Bezug auf die komplexen Zahlen:

In Bezug auf den reellen Anteil von :

In Bezug auf den imaginären Anteil von :

Die Java-Methode quadriere multipliziert eine komplexe Zahl mit sich selbst:

	/*
	 * Quadriert eine komplexe Zahl
	 * @param komplexeZahl: komplexe Zahl, die quadriert werden soll
	 */
	public static void quadriere (KomplexeZahl komplexeZahl)
	{
		double re2 = (komplexeZahl.re * komplexeZahl.re) - (komplexeZahl.im * komplexeZahl.im);
		double im2 = 2 * komplexeZahl.re * komplexeZahl.im;

		komplexeZahl.re = re2;
		komplexeZahl.im = im2;
	}

Das Betragsquadrat und der Betrag einer komplexen Zahl sind reellwertig (der Imaginäranteil ist also null), und diese können mit Hilfe des Satzes von Pythagoras berechnet werden:

Die folgende Methode betragsquadrat ermittelt das Betragsquadrat einer komplexen Zahl:

	/*
	 * Berechnet das Betragsquadrat einer komplexen Zahl
	 * @param komplexeZahl: komplexe Zahl, deren Betragsquadrat bestimmt werden soll
	 * @return: reelwertiges Betragsquadrat der komplexen Zahl
	 */
	public static double betragsquadrat (KomplexeZahl komplexeZahl)
	{
		double betragsquadrat = (komplexeZahl.re * komplexeZahl.re) + (komplexeZahl.im * komplexeZahl.im);
		return betragsquadrat;
	}

Mit dem folgenden Java-Programm in der Java-Klasse KomplexeZahl können die oben aufgeführten Rechenoperationen durchgeführt werden:

→ Java-Programm "KomplexeZahl"

Siehe auch:

Quadratische Polynome[Bearbeiten]

Ein quadratisches Polynom kann wie folgt als Funktion des Arguments dargestellt werden:

Hierbei können das Argument und der Koeffizient auch komplexe Zahlen sein.

Bei einer Polynomfolge wird das nächste Glied mit dem Index nach der folgenden Vorschrift aus dem aktuellen Glied mit dem Index berechnet:

mit

Ein quadratische Polynomfolge kann demnach wie folgt dargestellt werden:

Dieser Algorithmus ist in der folgenden Java-Methode berechneNaechstesPolynomglied umgesetzt:

	/*
	 * Berechnung des nächsten Polynomglieds mit z(n+1) = z(n)^2 + c
	 * @param z: Variable z des Polynoms
	 * @param c: Variable c des Polynoms
	 */
	private static void berechneNaechstesPolynomglied (KomplexeZahl z, KomplexeZahl c)
	{
		KomplexeZahl.quadriere (z);
		KomplexeZahl.addiereZu (z, c);
	}

Bei den Julia-Mengen wird das Verhalten dieser Polynomfolge für alle Startwerte in der komplexen Zahlenebene dahingehend untersucht, ob der Betrag dieser Folge beschränkt bleibt oder gegen unendlich divergiert. Für jeden Koeffizienten ergibt sich eine andere Julia-Menge.

Bei der Mandelbrot-Menge wird das Verhalten dieser Polynomfolge für alle Koeffizienten in der komplexen Zahlenebene dahingehend untersucht, ob der Betrag dieser Folge mit dem Startwert beschränkt bleibt oder gegen unendlich divergiert.

In einem Computerprogramm kann diese Beschränktheit mit einer kopfgesteuerten Schleife abgeschätzt werden, die prüft, ob der Betrag der Polynomfolge nach einer vorgegebenen Anzahl von Schleifendurchläufen einen ebenfalls vorgegebenen reellen Betrag nicht überschritten hat. Hier ein Beispiel mit der Methode iterationszahlBisSchranke in der Programmiersprache Java, das für beliebige komplexwertige Zahlen und aufgerufen werden kann:

	/*
	 * Ermittelt die Iterationszahl bis zum Erreichen einer Schranke oder einer maximalen Iterationszahl
	 * @param z: Variable z des Polynoms
	 * @param c: Variable c des Polynoms
	 * @return: Iterationszahl bis zum Erreichen von schrankeZ2 oder 
	 *          maximaleAnzahlDerIterationen, wenn schrankeZ2 nicht ueberschritten wurde
	 */
	private static int iterationszahlBisSchranke (KomplexeZahl z, KomplexeZahl c)
	{
		// Konstanten
		final int maximaleAnzahlDerIterationen = 100; // Maximale Anzahl zur Ermittlung der Divergenz der Polynomfolge	
		final int schrankeZ2 = 4; // Schranke fuer das Betragsquadrat von z zur Ermittlung der Divergenz der Polynomfolge

		// Zaehlvariable
		int zaehler = 0;
		while ((zaehler < maximaleAnzahlDerIterationen) && (KomplexeZahl.betragsquadrat (z) < schrankeZ2))
		{
			berechneNaechstesPolynomglied (z, c);
			zaehler++;
		}
		return zaehler;
	}

Es hat sich als praktisch erwiesen, für die Schranke der Beträge von z den Wert 2 beziehungsweise für die Betragsquadrate von z den Wert 4 zu wählen. Dicht an der Grenze der zusammenhängenden Gebiete kann es zu Polynomfolgen kommen, die langsam divergieren, so dass durch eine Erhöhung der maximalen Anzahl der Iterationen unter Umständen eine höhere Genauigkeit bei der Berechnung und Darstellung erreicht werden kann. Dies verlängert dann allerdings die Laufzeit der Berechnungen entsprechend.

Mit dem folgenden Java-Programm mit der Klasse FraktaleMenge können die oben erwähnten Rechenoperationen durchgeführt werden:

→ Java-Programm "FraktaleMenge"

Ausgabe[Bearbeiten]

Es wäre schade, wenn die Ergebnisse der oben aufgeführten Algorithmen nicht sichtbar gemacht werden können. Hierzu dient die unten in diesem Abschnitt abrufbare Java-Klasse FraktaleMengeAusgabe, die die Eigenschaften der Java-Klasse JFrame aus dem Java-Paket swing des Java-Moduls javax erbt. Dadurch wird aus der Oberklasse Window aus dem Java-Paket awt (Abkürzung für "abstract window toolkit") auch die öffentliche, typengebundene Methode paint geerbt, mit der die graphische Ausgabe erfolgt:

  • java.awt.Component
    • java.awt.Container
      • java.awt.Window
        • java.awt.Frame
          • javax.swing.JFrame
            • FraktaleMengeAusgabe

Mit Hilfe der Java-Klasse BufferedImage aus dem Java-Paket awt kann die Graphik auch gespeichert werden. Hierzu wird mit der Java-Klasse Iterator aus dem Java-Paket util wird die Bilddateiart PNG ausgewählt. Mit einigen Klassen aus dem Java-Paket javax.imageio wird dann das Speichern der unkomprimierten Bilddatei bewerkstelligt.

Mit dem Konstruktor FraktaleMengeAusgabe muss eine neu erzeugte Instanz der Klasse initialisiert werden:

	/* Konstruktor fuer die Initialisierung von Instanzen der Klasse FraktaleMengeAusgabe.
	 * @param c: Komplexwertiger Koeffizient der quadratischen Polynome
	 * @param minX: Kleinster x-Wert der rechteckigen Darstellung der fraktalen Menge
	 * @param maxX: Groesster x-Wert der rechteckigen Darstellung der fraktalen Menge
	 * @param minY: Kleinster y-Wert der rechteckigen Darstellung der fraktalen Menge
	 * @param maxY: Groesster y-Wert der rechteckigen Darstellung der fraktalen Menge
	 * @param bildhoehe: Die Anzahl der Bildpunkte in der Bildhoehe
	 * @param shiftFactor: Anteil des verschobenen oder gezoomten Bildausschnitts
	 */
	public FraktaleMengeAusgabe (KomplexeZahl c,
			double minX, double maxX, double minY, double maxY, int bildhoehe,
			double shiftFactor)

Die folgenden öffentlichen, klassengebundenen Methoden stehen zur Laufzeit zur Verfügung:

Modifikator Methode Beschreibung
void center () Zentriert die Menge auf den Ursprung (0, 0)
KomplexeZahl getComplexParameter ​() Gibt den komplexwertigen Parameter der fraktalen Menge zurück
int getKeyCode () Gibt den Code der gedrückten Taste zurück (siehe java.awt.event.KeyEvent @Field Summary / Fields)
double getMaxX () Gibt den maximalen reellen Wert der Menge zurück
double getMaxY () Gibt den maximalen imaginären Wert der Menge zurück
double getMinX () Gibt den minimalen reellen Wert der Menge zurück
double getMinY () Gibt den minimalen imaginären Wert der Menge zurück
double getShiftFactor () Gibt den aktuellen Verschiebungs- und Zoomfaktor in Anteilen des Bildausschnitts zurück
void init () Initialisiert die Menge
void repaint ​() Erneuert die Darstellung der Menge
void saveFile () Speichert die Menge in einer PNG-Datei
void setBorders ​(double minX, double maxX, double minY, double maxY) Setzt die minimalen und maximalen reelen (X) und imaginären (Y) Koordinaten
void setComplexParameter ​(KomplexeZahl c) Setzt den komplexwertigen Parameter der fraktalen Menge
void setShiftFactor ​(double shiftFactor) Setzt den Verschiebungs- und Zoomfaktor in Anteilen des Bildausschnitts festlegen; der Faktor muss größer als 0 und kleiner als 1 sein
void shiftDown () Ausschnitt wird nach unten verschoben
void shiftLeft () Ausschnitt wird nach links verschoben
void shiftRight( ) Ausschnitt wird nach rechts verschoben
void shiftUp () Ausschnitt wird nach oben verschoben
void zoomIn () In den Ausschnitt hineinzoomen
void zoomOut () Aus dem Ausschnitt herauszoomen

Mit dem folgenden Java-Programm mit der Klasse FraktaleMengeAusgabe können die fraktalen Mengen angezeigt oder als graphische PNG-Datei gespeichert werden:

→ Java-Programm "FraktaleMengeAusgabe"

Aufruf des Programms[Bearbeiten]

Die parameterlose, öffentliche Methode init () muss aufgerufen werden, um die entsprechende fraktale Menge zu berechnen und gegebenenfalls darzustellen oder zu speichern.

Mit dem folgenden Java-Code kann beispielsweise eine Julia-Menge mit dem Koeffizienten c = (-0.5142|0.5142) berechnet und graphisch ausgegeben werden:

	// Für Julia-Mengen ist der komplexwertige Parameter c auf einen für die Julia-Menge spezifischen Wert ungleich (0, 0) zu setzen.
	final KomplexeZahl c = new KomplexeZahl (-0.5142, 0.5142); // Julia-Menge
	double minRe = -2.2;
	double maxRe = 2.2;
	double minIm = -2.2;
	double maxIm = 2.2;
	double shiftFactor = 0.1;
	int bildhoehe = 1024;
	FraktaleMengeAusgabe fraktaleMengeAusgabe = new FraktaleMengeAusgabe (c, minRe, maxRe, minIm, maxIm, bildhoehe, shiftFactor);
	fraktaleMengeAusgabe.init ();

Mit dem folgenden Java-Code kann die Mandelbrot-Menge berechnet und graphisch ausgegeben werden:

	// Für die Mandelbrot-Menge ist der komplexwertige Parameter c auf (0, 0) zu setzen.
	final KomplexeZahl c = new KomplexeZahl (0, 0); // Mandelbrot-Menge
	double minRe = -2.2;
	double maxRe = 2.2;
	double minIm = -2.2;
	double maxIm = 2.2;
	double shiftFactor = 0.1;
	int bildhoehe = 1024;
	FraktaleMengeAusgabe fraktaleMengeAusgabe = new FraktaleMengeAusgabe (c, minRe, maxRe, minIm, maxIm, bildhoehe, shiftFactor);
	fraktaleMengeAusgabe.init ();

Mit dem Aufruf der klassengebundenen Methode safeFile () kann beispielsweise der aktuelle Ausschnitt der im Objekt fraktaleMengeAusgabe der Klasse FraktaleMengeAusgabe gespeicherten Menge als PNG-Datei gespeichert werden:

		fraktaleMengeAusgabe.saveFile ();

Mit der beispielhaften Methode centerAt0Key (FraktaleMengeAusgabe fraktaleMengeAusgabe) kann die Darstellung der Instanz fraktaleMengeAusgabe der Klasse FraktaleMengeAusgabe modifiziert werden. Das folgende Code-Beispiel bringt den Ursprungspunkt (0, 0) der Menge in die Mitte der graphischen Darstellung, wenn die Taste "0" (entweder auf der Haupttastatur (java.awt.event.KeyEvent.VK_0) oder auf dem Nummernblock (java.awt.event.KeyEvent.VK_NUMPAD0)) niedergedrückt ist:

	private static void centerAt0Key (FraktaleMengeAusgabe fraktaleMengeAusgabe)
	{
		int keyCode = fraktaleMengeAusgabe.getKeyCode ();
		if ((keyCode == java.awt.event.KeyEvent.VK_0) || (keyCode == java.awt.event.KeyEvent.VK_NUMPAD0))
		{
			fraktaleMengeAusgabe.center ();
		}
		fraktaleMengeAusgabe.repaint ();
	}

Nachwort[Bearbeiten]

Die hier angegebenen strukturierten Programmbeispiele können ohne große Umstände in jede andere strukturierte Programmiersprache übertragen werden. Durch die Modifikation und Ergänzung der angegebenen Algorithmen können sicherlich auch leicht ähnlich geartete Programmieraufgaben gelöst werden. Es wäre erfreulich, wenn dieses Wikibook zu einem besseren und tieferen Verständnis der strukturierten Programmierung und als Einstieg in die Mathematik fraktaler Mengen beitragen kann.

Widmung[Bearbeiten]

Diese Zusammenstellung ist dem deutschen Mathematiker und Physiker  Reinhart Behr (* 1928; † 2003) gewidmet. Der Hauptautor dankt ihm für seine Offenheit und Ansprechbarkeit als Gymnasiallehrer der Beethoven-Oberschule in Berlin-Lankwitz und für seine immerwährende Bereitschaft zur Förderung und Bildung seiner Schüler. Er konnte mit seinem beeindruckenden und breitgefächerten Wissen sowie mit der ihm eigenen und hervorragenden Didaktik auch anspruchsvolle Fragen meist aus dem Stand und umfassend beantworten.

Literatur[Bearbeiten]

  • Heinz-Otto Peitgen und Dietmar Saupe: The Science of Fractal Images, Springer, Heidelberg, 1988, ISBN 0-387-96608-0.
  • Reinhart Behr: Der Weg zur fraktalen Geometrie, Ernst Klett Schulbuchverlag, Stuttgart, 1989, ISBN 3-12-722410-9

Quelldateien[Bearbeiten]

KomplexeZahl[Bearbeiten]

/*
  Source file: KomplexeZahl.java
  Author: Markus Bautsch
  Licence: public domain
  Date: 9 April 2021
  Version: 1.0
  Programming language: Java
*/

/*
  Klasse für komplexe Zahlen mit einigen arithmetischen Operationen
*/

public class KomplexeZahl 
{
	// Die beiden Instanzvariablen re und im sind die Datenfelder des Datentyps KomplexeZahl
	public double re; // Realteil der komplexen Zahl
	public double im; // Imaginaerteil der komplexen Zahl

	/*
	 * Konstruktor zum Initialisieren einer Instanz einer komplexen Zahl
	 * @param re: Realteil der komplexen Zahl
	 * @param im: Imaginaerteil der komplexen Zahl
	 */
	public KomplexeZahl (double re, double im)
	{
		this.re = re;
		this.im = im;
	}

	/*
	 * Addiert komplexerSummand zu komplexeZahl und speichert das Ergebnis in komplexeZahl
	 * @param komplexeZahl: komplexe Zahl, zu der addiert wird
	 * @param komplexerSummand: komplexe Zahl, die zu komplexeZahl addiert wird
	 */
	public static void addiereZu (KomplexeZahl komplexeZahl, KomplexeZahl komplexerSummand)
	{
		double reSumme = komplexeZahl.re + komplexerSummand.re;
		double imSumme = komplexeZahl.im + komplexerSummand.im;

		komplexeZahl.re = reSumme;
		komplexeZahl.im = imSumme;
	}

	/*
	 * Quadriert eine komplexe Zahl
	 * @param komplexeZahl: komplexe Zahl, die quadriert werden soll
	 */
	public static void quadriere (KomplexeZahl komplexeZahl)
	{
		double re2 = (komplexeZahl.re * komplexeZahl.re) - (komplexeZahl.im * komplexeZahl.im);
		double im2 = 2 * komplexeZahl.re * komplexeZahl.im;

		komplexeZahl.re = re2;
		komplexeZahl.im = im2;
	}
	
	/*
	 * Berechnet das Betragsquadrat einer komplexen Zahl
	 * @param komplexeZahl: komplexe Zahl, deren Betragsquadrat bestimmt werden soll
	 * @return: reelwertiges Betragsquadrat der komplexen Zahl
	 */
	public static double betragsquadrat (KomplexeZahl komplexeZahl)
	{
		double betragsquadrat = (komplexeZahl.re * komplexeZahl.re) + (komplexeZahl.im * komplexeZahl.im);
		return betragsquadrat;
	}
}

FraktaleMenge[Bearbeiten]

/*
  Source file: FraktaleMenge.java
  Author: Markus Bautsch
  Licence: public domain
  Date: 9 April 2021
  Version: 1.0
  Programming language: Java
*/

/*
  Steuert die graphische Ausgabe von Julia- und Mandelbrot-Mengen
*/

public class FraktaleMenge 
{
	// Die Konstanten der Klasse
	public final static int maximaleAnzahlDerIterationen = 100; // Maximale Anzahl zur Ermittlung der Divergenz der Polynomfolge	
	private final static int schrankeZ2 = 4; // Schranke fuer das Betragsquadrat von z zur Ermittlung der Divergenz der Polynomfolge

	/*
	 * Berechnung des nächsten Polynomglieds mit z(n+1) = z(n)^2 + c
	 * @param z: Variable z des Polynoms
	 * @param c: Variable c des Polynoms
	 */
	private static void berechneNaechstesPolynomglied (KomplexeZahl z, KomplexeZahl c)
	{
		KomplexeZahl.quadriere (z);
		KomplexeZahl.addiereZu (z, c);
	}

	/*
	 * Ermittelt die Iterationszahl bis zum Erreichen einer Schranke oder einer maximalen Iterationszahl
	 * @param z: Variable z des Polynoms
	 * @param c: Variable c des Polynoms
	 * @return: Iterationszahl bis zum Erreichen von schrankeZ2 oder
	 *          maximaleAnzahlDerIterationen, wenn schrankeZ2 nicht ueberschritten wurde
	 */
	public static int iterationszahlBisSchranke (KomplexeZahl z, KomplexeZahl c)
	{
		int zaehler = 0;
		while ((zaehler < maximaleAnzahlDerIterationen) && (KomplexeZahl.betragsquadrat (z) < schrankeZ2))
		{
			berechneNaechstesPolynomglied (z, c);
			zaehler++;
		}
		return zaehler;
	}
}

FraktaleMengeAusgabe[Bearbeiten]

/*
  Source file: FraktaleMengeAusgabe.java
  Author: Markus Bautsch
  Licence: public domain
  Date: 3 November 2022
  Version: 1.1 / using method java.awt.Graphics.drawImage
  Revision: 27 May 2023
  Version: 2.1 (implementing key listener, PNG file compression, variable complex values)
  Programming language: Java
 */

/*
  Graphische Ausgabe von Julia- und Mandelbrot-Mengen.
  Mandelbrot-Mengen werden mit dem konstanten komplexwertigen Parameter c = (0, 0) gekennzeichnet.
 */

public final class FraktaleMengeAusgabe extends javax.swing.JFrame implements java.awt.event.KeyListener
{
	// serialVersionUID fuer die Serialisation, die in der Klasse javax.swing.JFrame implementiert ist.
	private final static long serialVersionUID = 1;

	// Klassenkonstanten
	private final static int offset = 10;
	private final static int offsetTop = 30;

	// Instanzkonstanten
	private KomplexeZahl c; 
	private boolean isMandelbrot;
	private double minX;
	private double maxX;
	private double minY;
	private double maxY;
	final private int pixelZahlX;
	final private int pixelZahlY;
	private double shiftFactor = 0.05; // Zum Zoomen und Verschieben des Bildes

	// Instanzvariablen
	private int keyCode;
	private java.awt.image.BufferedImage bufferedImage;
	private java.awt.image.WritableRaster imageRaster;

	/* Konstruktor fuer die Initialisierung von Instanzen der Klasse FraktaleMengeAusgabe.
	 * @param c: Komplexwertiger Koeffizient der quadratischen Polynome
	 * @param minX: Kleinster x-Wert der rechteckigen Darstellung der fraktalen Menge
	 * @param maxX: Groesster x-Wert der rechteckigen Darstellung der fraktalen Menge
	 * @param minY: Kleinster y-Wert der rechteckigen Darstellung der fraktalen Menge
	 * @param maxY: Groesster y-Wert der rechteckigen Darstellung der fraktalen Menge
	 * @param bildhoehe: Die Anzahl der Bildpunkte in der Bildhoehe
	 * @param shiftFactor: Anteil des verschobenen oder gezoomten Bildausschnitts
	 */
	public FraktaleMengeAusgabe (KomplexeZahl c,
			double minX, double maxX, double minY, double maxY, int bildhoehe,
			double shiftFactor)
	{
		super ("Fraktale Menge");
		this.setComplexParameter (c);
		this.minX = minX;
		this.maxX = maxX;
		this.minY = minY;
		this.maxY = maxY;
		this.pixelZahlY = bildhoehe;
		this.pixelZahlX = (int) (bildhoehe / (maxY - minY) * (maxX - minX));
		this.shiftFactor = shiftFactor;
		this.keyCode = 0;
		this.bufferedImage = new java.awt.image.BufferedImage (this.pixelZahlX, this.pixelZahlY, java.awt.image.BufferedImage.TYPE_INT_RGB);
		this.imageRaster = bufferedImage.getWritableTile (0, 0);
		this.setDefaultCloseOperation (javax.swing.WindowConstants.EXIT_ON_CLOSE);
		this.addKeyListener (this);
	}

	// Methode setName fuer die Bestimmung des aktuellen Titels der Instanz von FraktaleMengeAusgabe.
	private void setName ()
	{
		java.lang.String fractalSetName;
		if (this.isMandelbrot)
		{
			fractalSetName = "Mandelbrot-Menge";
		}
		else
		{
			fractalSetName = "Julia-Menge" + "_c.re_" + this.c.re + "_c.im_" + this.c.im;
		}
		fractalSetName = fractalSetName + "_re_" + this.minX + "_" + this.maxX + "_im_" + this.minY + "_" + this.maxY;
		this.setTitle (fractalSetName);
	}

	// Methode saveFile für das Speichern einer fraktalen Graphik in einer graphischen PNG-Datei
	public void saveFile ()
	{
		final java.lang.String fileExtension = "png";
		java.util.Iterator <javax.imageio.ImageWriter> iter = javax.imageio.ImageIO.getImageWritersByFormatName (fileExtension);
		javax.imageio.ImageWriter imageWriter = (javax.imageio.ImageWriter) iter.next ();
		javax.imageio.ImageWriteParam imageWriteParam = imageWriter.getDefaultWriteParam ();
		imageWriteParam.setCompressionMode (javax.imageio.ImageWriteParam.MODE_DEFAULT); // for compressed file
		try
		{
			java.lang.String fileName = this.getTitle () + "." + fileExtension;
			javax.imageio.stream.FileImageOutputStream fileImageOutputStream = new javax.imageio.stream.FileImageOutputStream (new java.io.File (fileName));
			imageWriter.setOutput (fileImageOutputStream);
			javax.imageio.IIOImage iIOimg = new javax.imageio.IIOImage ((java.awt.image.RenderedImage) bufferedImage, null, null);
			java.lang.System.out.println ("Save " + fileName);
			imageWriter.write (null, iIOimg, imageWriteParam);
		}
		catch (java.lang.Exception exception)
		{
			exception.printStackTrace ();
		}
	}

	/*
	 * Bestimmt die Farbe eines darzustellenden Punktes aus der Zahl der durchgefuehrten Iterationen in der Polynomfolge
	 * @param zahlZurFarbbestimmung: Zahl der durchgefuehrten Iterationen, für die eine Farbbestimmung durchgeführt werden aoll
	 * @return: Farbe vom Datentyp java.awt.Color
	 */
	private static int [] bestimmeAusgabefarbe (int zahlZurFarbbestimmung)
	{
		final int hell = 255;
		final int schwellfaktor1 = 8;
		final int schwellfaktor2 = 16;
		final int schwelle1 = hell / schwellfaktor1;
		final int schwelle2 = hell / schwellfaktor2;

		int helligkeit = hell * zahlZurFarbbestimmung / FraktaleMenge.maximaleAnzahlDerIterationen; // liegt im Intervall [0 .. HELL]

		int [] farbe = {0, 0, 0}; // Vorgabe = schwarz fuer Hintergrund, wenn Polynomfolge als nicht divergent angesehen wird

		if (zahlZurFarbbestimmung < FraktaleMenge.maximaleAnzahlDerIterationen)
		{
			if (helligkeit > schwelle1) // Polynomfolge divergiert langsam
			{
				farbe [0] = hell; // rot
				farbe [1] = hell; // gruen
			}
			else if (helligkeit > schwelle2) // Polynomfolge divergiert schneller
			{
				farbe [0] = schwellfaktor1 * helligkeit; // rot
				farbe [1] = helligkeit; // gruen
			}
			else // Polynomfolge divergiert schnell
			{
				farbe [0] = schwellfaktor2 * helligkeit; // rot
				farbe [1] = helligkeit; // gruen
			}
			farbe [2] = helligkeit; // blau
		}
		return farbe;
	}

	/*
	 * Zeichnet einen farbigen Punkt in das Bild
	 * @param frame: Frame von Datentyp FraktaleMengeAusgabe
	 * @param z: Variable z des Polynoms
	 * @param c: Variable c des Polynoms
	 * @param xWert Der X-Wert an dem der Punkt gemalt werden soll
	 * @param yWert Der Y-Wert an dem der Punkt gemalt werden soll
	 */
	private static void zeichnePunkt (FraktaleMengeAusgabe frame, KomplexeZahl z, KomplexeZahl c, int xWert, int yWert)
	{
		int zahlZurFarbbestimmung = FraktaleMenge.iterationszahlBisSchranke (z, c);
		int [] farbe = bestimmeAusgabefarbe (zahlZurFarbbestimmung);
		frame.imageRaster.setPixel (xWert, yWert, farbe);
	}

	/*
	 * Berechnet den Skalierungsfaktor auf einer Koordinatenachse
	 * @param min: Der minimale Wert auf der Koordinatenachse
	 * @param max: Der maximale Wert auf der Koordinatenachse
	 * @param pixelZahl: Die gewuenschte Anzahl der Pixel auf der Koordinatenachse
	 * @return: Skalierungsfaktor auf der Koordinatenachse
	 */
	private static double berechneSkalierungsfaktor (double min, double max, long pixelZahl)
	{
		double skalierungsfaktor = (max - min) / pixelZahl;
		return skalierungsfaktor;
	}

	/*
	 * Transformiert eine x-Koordinate aus der Pixel-Ebene in die mathematische Ebene
	 * @param minX: Der minimale x-Wert in der mathematischen Ebene
	 * @param skalierungsfaktorX: Skalierungsfaktor von der Pixel-Ebene in die mathematische Ebene in x-Richtung
	 * @param pixelKoordinateX: Die x-Koordinate in der Pixel-Ebene
	 * @return: Die x-Koordinate in der mathematischen Ebene
	 */
	private static double koordinatentransformationX (double minX, double skalierungsfaktorX, long pixelKoordinateX)
	{
		double xKoordinate = minX + (pixelKoordinateX * skalierungsfaktorX);
		return xKoordinate;
	}

	/*
	 * Transformiert eine y-Koordinate aus der Pixel-Ebene in die mathematische Ebene
	 * @param maxY: Der maximale y-Wert auf der mathematischen Ebene
	 * @param skalierungsfaktorY: Skalierungsfaktor von der Pixel-Ebene in die mathematische Ebene in y-Richtung
	 * @param pixelKoordinateY: Die y-Koordinate in der Pixel-Ebene
	 * @return: Die y-Koordinate in der mathematischen Ebene
	 */
	private static double koordinatentransformationY (double maxY, double skalierungsfaktorY, long pixelKoordinateY)
	{
		double yKoordinate = maxY - (pixelKoordinateY * skalierungsfaktorY);
		return yKoordinate;
	}

	/*
	 * Zeichnet eine fraktale Menge (Julia-Menge oder Mandelbrot-Menge)
	 * @param fraktakeMenge: fraktale Menge
	 * @param graphics: Graphikelement
	 */
	private static void fraktaleMenge (FraktaleMengeAusgabe fraktaleMenge)
	{
		final double skalierungX = berechneSkalierungsfaktor (fraktaleMenge.minX, fraktaleMenge.maxX, fraktaleMenge.pixelZahlX);
		final double skalierungY = berechneSkalierungsfaktor (fraktaleMenge.minY, fraktaleMenge.maxY, fraktaleMenge.pixelZahlY);

		KomplexeZahl c = fraktaleMenge.c; // Parameter c der fraktalen Menge
		KomplexeZahl z = new KomplexeZahl (0,0); // Parameter z der fraktalen Menge

		int ix; // Zaehler für Spalten in x-Richtung nach rechts
		int iy; // Zaehler für Zeilen in y-Richtung nach unten

		iy = 0; // oben starten
		while (iy < fraktaleMenge.pixelZahlY)
		{			
			ix = 0; // links starten
			while (ix < fraktaleMenge.pixelZahlX)
			{
				double re = koordinatentransformationX (fraktaleMenge.minX, skalierungX, ix);
				double im = koordinatentransformationY (fraktaleMenge.maxY, skalierungY, iy);
				if (fraktaleMenge.isMandelbrot) // Mandelbrot-Menge
				{
					z.re = 0; // Realteil von z0 ist immer gleich 0
					z.im = 0; // Imaginaerteil von z0 ist immer gleich 0
					c.re = re; // Realteil von c ist immer gleich der x-Koordinate
					c.im = im; // Imaginaerteil von c ist immer gleich der y-Koordinate
				}
				else // Julia-Menge
				{
					z.re = re; // Realteil von z ist immer gleich der x-Koordinate
					z.im = im; // Imaginaerteil von z ist immer gleich der y-Koordinate	
				}
				zeichnePunkt (fraktaleMenge, z, c, ix, iy);
				ix++;
			}
			iy++;
		}
	}

	/*
	 * Zeichnet eine fraktale Menge
	 * @param graphics Graphisches Element
	 */
	public void paint (java.awt.Graphics graphics)
	{
		// Rahmengröße, Hintergrund und das Layout werden gesetzt.
		this.setName ();
		fraktaleMenge (this);
		graphics.drawImage (bufferedImage, offset, offset + offsetTop, null);
	}

	// Methode init fuer die Initialisierung der graphischen Ausgabe einer Instanz von FraktaleMengeAusgabe.
	public void init ()
	{
		// Rahmengroesse, Hintergrund und das Layout werden gesetzt
		this.setSize (this.pixelZahlX, this.pixelZahlY);
		this.setBackground (java.awt.Color.WHITE);
		java.awt.image.BufferedImage bufferedImage = new java.awt.image.BufferedImage (this.pixelZahlX, this.pixelZahlY, java.awt.image.BufferedImage.TYPE_INT_RGB);
		java.awt.Graphics2D graphics2D = bufferedImage.createGraphics ();
		this.setVisible (true);
		this.setAlwaysOnTop (true);
		this.paint (graphics2D);
	}

	@Override
	public void keyPressed (java.awt.event.KeyEvent event)
	{
		this.keyCode = event.getKeyCode ();
	}

	@Override
	public void keyReleased(java.awt.event.KeyEvent event)
	{
	}

	@Override
	public void keyTyped (java.awt.event.KeyEvent event)
	{
	}

	public int getKeyCode ()
	{
		int returnKeyCode = this.keyCode;
		this.keyCode = 0;
		return returnKeyCode;
	}

	public double getMinX ()
	{
		return this.minX;
	}

	public double getMaxX ()
	{
		return this.maxX;
	}

	public double getMinY ()
	{
		return this.minY;
	}

	public double getMaxY ()
	{
		return this.maxY;
	}

	public void setBorders (double minX, double maxX, double minY, double maxY)
	{
		this.minX = minX;
		this.maxX = maxX;
		this.minY = minY;
		this.maxY = maxY;
	}

	public KomplexeZahl getComplexParameter ()
	{
		KomplexeZahl c = this.c;
		return c;
	}

	public void setComplexParameter (KomplexeZahl c)
	{
		this.c = c;
		this.isMandelbrot = (KomplexeZahl.betragsquadrat (c) == 0);
	}

	private double getXShift ()
	{
		double minX = this.getMinX ();
		double maxX = this.getMaxX ();
		double shiftX = (maxX - minX) * shiftFactor;
		return shiftX;
	}

	private double getYShift ()
	{
		double minY = this.getMinY ();
		double maxY = this.getMaxY ();
		double shiftY = (maxY - minY) * shiftFactor;
		return shiftY;
	}

	public double getShiftFactor ()
	{
		double shiftFactor = this.shiftFactor;
		return shiftFactor;
	}

	public void setShiftFactor (double shiftFactor)
	{
		this.shiftFactor = shiftFactor;
	}

	public void center ()
	{
		double minX = this.getMinX ();
		double maxX = this.getMaxX ();
		double deltaX2 = (maxX - minX) / 2;

		double minY = this.getMinY ();
		double maxY = this.getMaxY ();
		double deltaY2 = (maxY - minY) / 2;

		this.setBorders (-deltaX2, deltaX2, -deltaY2, deltaY2);
	}

	public void zoomIn ()
	{
		double shiftX = getXShift ();
		double minX = this.getMinX () + shiftX;
		double maxX = this.getMaxX () - shiftX;

		double shiftY = getYShift ();
		double minY = this.getMinY () + shiftY;
		double maxY = this.getMaxY () - shiftY;

		this.setBorders (minX, maxX, minY, maxY);
	}

	public void zoomOut ()
	{
		double shiftX = getXShift ();
		double minX = this.getMinX () - shiftX;
		double maxX = this.getMaxX () + shiftX;

		double shiftY = getYShift ();
		double minY = this.getMinY () - shiftY;
		double maxY = this.getMaxY () + shiftY;

		this.setBorders (minX, maxX, minY, maxY);
	}

	public void shiftRight ()
	{
		double shiftX = getXShift ();
		double minX = this.getMinX () + shiftX;
		double maxX = this.getMaxX () + shiftX;

		double minY = this.getMinY ();
		double maxY = this.getMaxY ();

		this.setBorders (minX, maxX, minY, maxY);
	}

	public void shiftLeft ()
	{
		double shiftX = getXShift ();
		double minX = this.getMinX () - shiftX;
		double maxX = this.getMaxX () - shiftX;

		double minY = this.getMinY ();
		double maxY = this.getMaxY ();

		this.setBorders (minX, maxX, minY, maxY);
	}

	public void shiftUp ()
	{
		double minX = this.getMinX ();
		double maxX = this.getMaxX ();

		double shiftY = getYShift ();
		double minY = this.getMinY () + shiftY;
		double maxY = this.getMaxY () + shiftY;

		this.setBorders (minX, maxX, minY, maxY);
	}

	public void shiftDown ()
	{
		double minX = this.getMinX ();
		double maxX = this.getMaxX ();

		double shiftY = getYShift ();
		double minY = this.getMinY () - shiftY;
		double maxY = this.getMaxY () - shiftY;

		this.setBorders (minX, maxX, minY, maxY);
	}
}