Added the mandelbrot calculator

This commit is contained in:
nora 2020-11-17 19:48:01 +01:00
parent 2234919b69
commit 64dc682257
7 changed files with 360 additions and 6 deletions

View file

@ -0,0 +1,56 @@
package mandelbrotCalculator;
public class CNumber {
// values
private double real;
private double imag;
// constructor
public CNumber(double real, double imag) {
this.real = real;
this.imag = imag;
}
public CNumber() {
this.real = 0;
this.imag = 0;
}
// multiply 2 complex numbers
public static CNumber multiply(CNumber a, CNumber b) {
CNumber result = new CNumber();
// DO NOT TOUCH, COPIED IT this is some weird math stuff
result.real = a.real * a.real - b.imag * b.imag;
result.imag = a.real * b.imag + b.real * a.imag;
return result;
}
public static CNumber add(CNumber a, CNumber b) {
CNumber result = new CNumber();
result.real = a.real + b.real;
result.imag = a.imag + b.imag;
return result;
}
public double getReal() {
return real;
}
public void setReal(double real) {
this.real = real;
}
public double getImag() {
return imag;
}
public void setImag(double imag) {
this.imag = imag;
}
}

View file

@ -0,0 +1,138 @@
package mandelbrotCalculator;
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
public class CalculationThread extends Thread {
int threadNumber;
int threadAmount;
int frameAmount;
int width;
int height;
int iterations;
int threshold;
double[][] zoomValues;
CNumber[][] samples;
public void run() {
long totalStartTime = System.currentTimeMillis();
samples = new CNumber[width][height];
for (int frameCounter = threadNumber; frameCounter < frameAmount; frameCounter += threadAmount) {
long startTime = System.currentTimeMillis();
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
File f = null;
for (int i = 0; i < width; i++) {
for (int j = 0; j < height; j++) {
samples[i][j] = new CNumber(); // fill the array
samples[i][j].setReal(zoomValues[2][frameCounter] + zoomValues[0][frameCounter] * i); // calculate the position on the real numberline
samples[i][j].setImag(zoomValues[3][frameCounter] - zoomValues[1][frameCounter] * j); // calculate the position on the imaginary numberline
}
}
// calculate values
double[][] values = new double[width][height]; // new array of booleans for the drawing
for (int i = 0; i < width; i++) {
for (int j = 0; j < height; j++) {
values[i][j] = checkMandelbrot(samples[i][j], iterations, threshold); // check if the number is inside of th set
}
}
createImage(image, frameCounter, width, height, values, iterations);
long frameTime = System.currentTimeMillis() - startTime;
System.out.println("------------------------Frame " + frameCounter + " finished in " + ((double) frameTime / 1000) + "s------------------------");
}
long totalTime = System.currentTimeMillis() - totalStartTime;
System.out.println("Thread " + threadNumber + " completed. Process took " + ((double) totalTime / 1000) + "s");
}
public CalculationThread(int number, int threads, int frames, int widthC, int heightC, int iterationsC, int thresholdC, double[][] zoomValuesC) {
this.threadNumber = number;
this.threadAmount = threads;
this.frameAmount = frames;
this.width = widthC;
this.height = heightC;
this.iterations = iterationsC;
this.threshold = thresholdC;
this.zoomValues = zoomValuesC;
}
/**
* Creates an image from the calculated values
* @param image the image to be used
* @param counter the frame number of the image
* @param width width of the image
* @param height height of the image
* @param values the values of every pixel
* @param iterations the amount of interations
*/
void createImage(BufferedImage image, int counter, int width, int height, double[][] values, int iterations) {
//System.out.println("Frame: " + counter + " | Started creating image...");
int p0 = getColorAsInt(0, 0, 0, 0);
int t0 = -1;
for (int i = 0; i < width; i++) {
for (int j = 0; j < height; j++) {
//for every frame:
double n = values[i][j];
float hue = (float) n / iterations;
Color color = Color.getHSBColor(hue, 1, 1);
image.setRGB(i, j, color.getRGB());
if (n == t0) {
image.setRGB(i, j, p0);
}
}
}
try {
File f = new File("C:\\Users\\nilsh\\Desktop\\testordner/sterbi" + counter + ".png");
ImageIO.write(image, "png", f);
System.out.println(f.getAbsolutePath());
} catch (IOException e) {
e.printStackTrace();
}
}
int checkMandelbrot(CNumber number, int iterations, double threshold) {
//returns -1 if the number is in the set, returns the number of iterations before it reached the threshold if it is not in the set
// start
CNumber n = new CNumber();
int reached = -1;
// first
n = CNumber.add(n, number);
for (int i = 0; i < iterations; i++) {
n = CNumber.add(CNumber.multiply(n, n), number); // CNumber.multiply(n, n)
if ((n.getReal() + n.getImag()) > threshold) {
reached = i;
break;
}
}
return reached;
}
public int getColorAsInt(int a, int r, int g, int b) {
return (a << 24) | (r << 16) | (g << 8) | b;
}
}

View file

@ -0,0 +1,154 @@
package mandelbrotCalculator;
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
public class MandelbrotSet {
private double[][] interestingPoints = {{-0.75, 0}, {-0.77568377, 0.13646737}, {-1.74995768370609350360221450607069970727110579726252077930242837820286008082972804887218672784431700831100544507655659531379747541999999995, 0.00000000000000000278793706563379402178294753790944364927085054500163081379043930650189386849765202169477470552201325772332454726999999995}};
//constructor parameter variables
private int pointNumber;
private double zoomSpeed; // >1
private int frames;
//default values that can be changed
private double pointX = 0;
private double pointY = 0;
private double zoom = 1;
private int width = 1920;
private int threshold = 100;
private float ratio = 2 / 3f;
private int threadAmount = 10;
//only declared
private int height;
private int iterations;
/**
* Create a new Mandelbrot set manager
* @param pointNumber The point number (-1 to set a custom point afterwards)
* @param quality The quality (0-4, any other value sets the iterations to this value directly
* @param zoomSpeed By how much the zoom value is multiplied every frame
* @param frames The number of frames to be calculated
*/
public MandelbrotSet(int pointNumber, int quality, double zoomSpeed, int frames) {
this.pointNumber = pointNumber;
this.zoomSpeed = zoomSpeed;
this.frames = frames;
height = (int) ((float) width * ratio);
iterations = switch (quality){
case 0 -> 50;
case 1 -> 100;
case 2 -> 500;
case 3 -> 1000;
case 4 -> 5000;
default -> quality;
};
}
public MandelbrotSet(){
this(2, 2, 1.2, 1);
}
public void startMandelbrot() {
double forceCenterX;
double forceCenterY;
if(pointNumber == -1){
forceCenterX = pointX;
forceCenterY = pointY;
} else {
forceCenterX = interestingPoints[pointNumber][0];
forceCenterY = interestingPoints[pointNumber][1];
}
// TIME
long startTime = System.currentTimeMillis();
double[][] zoomValues = zoomValues(frames, width, height, forceCenterX, forceCenterY, zoomSpeed, zoom);
//create the threads
CalculationThread[] threads = new CalculationThread[threadAmount];
for (int i = 0; i < threadAmount; i++) {
threads[i] = new CalculationThread(i, threadAmount, frames, width, height, iterations, threshold, zoomValues);
threads[i].start();
}
// TIME should probably not be here and serves no practical purpose but that doesn't stop me from keeping it here
long endTime = System.currentTimeMillis();
long completionTimeLong = endTime - startTime;
double completionTimeSec = (double) completionTimeLong / 1000.0;
System.out.println("Prepared " + frames + " frame/s in " + completionTimeSec + "s");
}
/**
* Creates a double array of every corner position
*
* @param frames the number of frames
* @param width width of the image
* @param height height of the image
* @param centerX the real coordinates of the center to be zoomed in
* @param centerY the imaginary coordinates of the center to be zoomed in
* @param zoomSpeed speed of the zoom
* @param zoom the initial zoom
* @return returns the corner coordinates
*/
double[][] zoomValues(int frames, int width, int height, double centerX, double centerY, double zoomSpeed, double zoom) {
//values, frames
//values: 0 - stepSizeX, 1 - stepSizeY, 2 - offsetX, 3 - offsetY
double[][] zoomValues = new double[4][frames];
for (int frameCounter = 0; frameCounter < frames; frameCounter++) {
zoomValues[0][frameCounter] = (3 / (float) width) / zoom;
zoomValues[1][frameCounter] = (2 / (float) height) / zoom;
zoomValues[2][frameCounter] = centerX - width / 2 * zoomValues[0][frameCounter];
zoomValues[3][frameCounter] = -(centerY - height / 2 * zoomValues[1][frameCounter]);
zoom *= zoomSpeed;
}
return zoomValues;
}
//SETTER
public void setZoom(double zoom) {
this.zoom = zoom;
}
public void setWidth(int width) {
this.width = width;
}
public void setThreshold(int threshold) {
this.threshold = threshold;
}
public void setRatio(float ratio) {
this.ratio = ratio;
}
public void setThreadAmount(int threadAmount) {
this.threadAmount = threadAmount;
}
public void setPointX(double pointX) {
this.pointX = pointX;
}
public void setPointY(double pointY) {
this.pointY = pointY;
}
}

View file

@ -1,8 +1,9 @@
package sample;
package ui;
import javafx.event.ActionEvent;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import mandelbrotCalculator.MandelbrotSet;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
@ -21,4 +22,9 @@ public class Controller {
setPreview.setImage(image);
currentFrame++;
}
public void startCalculation(ActionEvent actionEvent) {
MandelbrotSet m = new MandelbrotSet(2, 1, 3, 1);
m.startMandelbrot();
}
}

View file

@ -1,4 +1,4 @@
package sample;
package ui;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
@ -13,7 +13,7 @@ public class Main extends Application {
Parent root = FXMLLoader.load(getClass().getResource("sample.fxml"));
primaryStage.setTitle("Mandelbrot");
primaryStage.setScene(new Scene(root, 1800, 900));
primaryStage.setScene(new Scene(root, 500, 300));
primaryStage.show();
}

View file

@ -4,9 +4,9 @@
<?import javafx.scene.image.*?>
<?import javafx.scene.layout.*?>
<GridPane alignment="center" hgap="10.0" prefHeight="143.0" prefWidth="107.0" stylesheets="/sample/sample.css" vgap="10" xmlns="http://javafx.com/javafx/11.0.1" xmlns:fx="http://javafx.com/fxml/1" fx:controller="sample.Controller">
<Button onAction="#showImage" prefHeight="25.0" prefWidth="110.0" text="Start" GridPane.rowIndex="1" />
<ImageView fx:id="setPreview" fitWidth="1000" preserveRatio="true" />
<GridPane alignment="center" hgap="10.0" prefHeight="143.0" prefWidth="107.0" stylesheets="/ui/sample.css" vgap="10" xmlns="http://javafx.com/javafx/11.0.1" xmlns:fx="http://javafx.com/fxml/1" fx:controller="ui.Controller">
<Button onAction="#startCalculation" prefHeight="25.0" prefWidth="110.0" text="Start" GridPane.rowIndex="1" />
<ImageView fx:id="setPreview" fitWidth="100" preserveRatio="true" />
<columnConstraints>
<ColumnConstraints />
</columnConstraints>