From 64dc682257305655b3a1d59aea35b287610feab9 Mon Sep 17 00:00:00 2001 From: Nilstrieb Date: Tue, 17 Nov 2020 19:48:01 +0100 Subject: [PATCH] Added the mandelbrot calculator --- src/mandelbrotCalculator/CNumber.java | 56 +++++++ .../CalculationThread.java | 138 ++++++++++++++++ src/mandelbrotCalculator/MandelbrotSet.java | 154 ++++++++++++++++++ src/{sample => ui}/Controller.java | 8 +- src/{sample => ui}/Main.java | 4 +- src/{sample => ui}/sample.css | 0 src/{sample => ui}/sample.fxml | 6 +- 7 files changed, 360 insertions(+), 6 deletions(-) create mode 100644 src/mandelbrotCalculator/CNumber.java create mode 100644 src/mandelbrotCalculator/CalculationThread.java create mode 100644 src/mandelbrotCalculator/MandelbrotSet.java rename src/{sample => ui}/Controller.java (74%) rename src/{sample => ui}/Main.java (87%) rename src/{sample => ui}/sample.css (100%) rename src/{sample => ui}/sample.fxml (52%) diff --git a/src/mandelbrotCalculator/CNumber.java b/src/mandelbrotCalculator/CNumber.java new file mode 100644 index 0000000..d9ec11d --- /dev/null +++ b/src/mandelbrotCalculator/CNumber.java @@ -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; + } +} \ No newline at end of file diff --git a/src/mandelbrotCalculator/CalculationThread.java b/src/mandelbrotCalculator/CalculationThread.java new file mode 100644 index 0000000..0ce1332 --- /dev/null +++ b/src/mandelbrotCalculator/CalculationThread.java @@ -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; + } + +} diff --git a/src/mandelbrotCalculator/MandelbrotSet.java b/src/mandelbrotCalculator/MandelbrotSet.java new file mode 100644 index 0000000..6514004 --- /dev/null +++ b/src/mandelbrotCalculator/MandelbrotSet.java @@ -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; + } +} \ No newline at end of file diff --git a/src/sample/Controller.java b/src/ui/Controller.java similarity index 74% rename from src/sample/Controller.java rename to src/ui/Controller.java index 1d545f6..66951b0 100644 --- a/src/sample/Controller.java +++ b/src/ui/Controller.java @@ -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(); + } } diff --git a/src/sample/Main.java b/src/ui/Main.java similarity index 87% rename from src/sample/Main.java rename to src/ui/Main.java index a9d7eae..69dd4fd 100644 --- a/src/sample/Main.java +++ b/src/ui/Main.java @@ -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(); } diff --git a/src/sample/sample.css b/src/ui/sample.css similarity index 100% rename from src/sample/sample.css rename to src/ui/sample.css diff --git a/src/sample/sample.fxml b/src/ui/sample.fxml similarity index 52% rename from src/sample/sample.fxml rename to src/ui/sample.fxml index 9d4caee..af166ca 100644 --- a/src/sample/sample.fxml +++ b/src/ui/sample.fxml @@ -4,9 +4,9 @@ - -