/*
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);
}
}