From 476bd0728f1f2e9cb7dac8fbaed3f3fb731ce9ad Mon Sep 17 00:00:00 2001 From: Samuel Abramov Date: Thu, 9 Nov 2023 12:15:43 +0100 Subject: [PATCH] feat(#2):Implement Newton Fractal Generator #2 --- build.gradle.kts => build.gradle | 9 + readme.md | 1 + settings.gradle.kts | 2 +- .../java/de/fractalitylab/FractalityLab.java | 5 +- .../generators/BurningShipGenerator.java | 35 ++- .../generators/JuliaGenerator.java | 10 +- .../generators/MandelbrotGenerator.java | 68 +++--- .../generators/NewtonFractalGenerator.java | 201 ++++++++++++++++++ .../generators/SierpinskiGasketGenerator.java | 6 +- .../generators/TricornGenerator.java | 4 + 10 files changed, 289 insertions(+), 52 deletions(-) rename build.gradle.kts => build.gradle (84%) create mode 100644 src/main/java/de/fractalitylab/generators/NewtonFractalGenerator.java diff --git a/build.gradle.kts b/build.gradle similarity index 84% rename from build.gradle.kts rename to build.gradle index 60ea74f..1892f6b 100644 --- a/build.gradle.kts +++ b/build.gradle @@ -11,6 +11,15 @@ java { targetCompatibility = JavaVersion.VERSION_17 } +jar { + manifest { + attributes( + 'Main-Class': 'de.fractalitylab.FractalityLab' + ) + } +} + + repositories { mavenCentral() } diff --git a/readme.md b/readme.md index 5283f4b..1e22ff1 100644 --- a/readme.md +++ b/readme.md @@ -12,6 +12,7 @@ FractalityLab is a powerful Java-based tool for generating datasets of fractal i ![alt text](https://hc-linux.eu/edux/0b69dff9-68fd-4ff2-bea4-810a452b71cc.png) ![alt text](https://hc-linux.eu/edux/2d483ca2-3579-428e-8dcc-3034208a801c.png) ![alt text](https://hc-linux.eu/edux/9b529a2f-5e63-4dd9-939c-12a09dec41e4.png) +![alt text](https://hc-linux.eu/edux/e4c215af-3f02-4250-b1af-7d23b52dc15f.png) ### Download You can download a dataset of 1000 images (256x256) for each class generated with FractalityLab here diff --git a/settings.gradle.kts b/settings.gradle.kts index 199c34c..dbce442 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,2 +1,2 @@ -rootProject.name = "MandelbrotGan" +rootProject.name = "FractalityLab" diff --git a/src/main/java/de/fractalitylab/FractalityLab.java b/src/main/java/de/fractalitylab/FractalityLab.java index 04beddf..9a66c87 100644 --- a/src/main/java/de/fractalitylab/FractalityLab.java +++ b/src/main/java/de/fractalitylab/FractalityLab.java @@ -21,11 +21,12 @@ public static void main(String[] args) { config = parseArguments(args); ImageGenerator[] imageGenerators = { + new TricornGenerator(), new MandelbrotGenerator(), new JuliaGenerator(), new BurningShipGenerator(), - new TricornGenerator(), - new SierpinskiGasketGenerator() + new SierpinskiGasketGenerator(), + new NewtonFractalGenerator(), }; List allDataElements = generateAllDataElements(imageGenerators); diff --git a/src/main/java/de/fractalitylab/generators/BurningShipGenerator.java b/src/main/java/de/fractalitylab/generators/BurningShipGenerator.java index d65e198..1ae265c 100644 --- a/src/main/java/de/fractalitylab/generators/BurningShipGenerator.java +++ b/src/main/java/de/fractalitylab/generators/BurningShipGenerator.java @@ -2,8 +2,6 @@ import de.fractalitylab.data.ImageWriter; import de.fractalitylab.data.DataElement; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; import java.awt.*; import java.util.List; @@ -11,23 +9,31 @@ import java.util.ArrayList; import java.util.UUID; import java.util.concurrent.ThreadLocalRandom; +import java.util.logging.Logger; import java.util.stream.IntStream; public class BurningShipGenerator implements ImageGenerator{ - private static final Logger LOGGER = LogManager.getLogger(BurningShipGenerator.class); + private static final Logger LOGGER = Logger.getLogger(BurningShipGenerator.class.getName()); + private final ThreadLocalRandom random = ThreadLocalRandom.current(); @Override public List generateImage(int width, int height, int maxIterations, int numberOfImages) { List result = new ArrayList<>(); - IntStream.range(1, numberOfImages + 1).parallel().forEach(imageNumber -> { - LOGGER.info("Generating Burning Ship image " + imageNumber + "..."); - BufferedImage image = generateSingleImage(width, height, maxIterations*10 ); + IntStream.range(1, numberOfImages + 1).forEach(imageNumber -> { + BufferedImage image; + do { + image = generateSingleImage(width, height, maxIterations*10 ); + + } while (!checkBlackImage(image)); // Retry if the image is predominantly black + + UUID uuid = UUID.randomUUID(); ImageWriter.writeImage("burningship", uuid.toString(), image); result.add(new DataElement(uuid.toString(), "burningship")); }); + LOGGER.info("BurningShip generation finished."); return result; } @@ -83,6 +89,23 @@ private BufferedImage generateSingleImage(int width, int height, int maxIteratio return image; } + // Add this method to your BurningShipGenerator class + private boolean checkBlackImage(BufferedImage image) { + int width = image.getWidth(); + int height = image.getHeight(); + int[] pixels = new int[width * height]; + image.getRGB(0, 0, width, height, pixels, 0, width); + + int blackPixelThreshold = (int) (width * height * 0.93); + long blackPixelCount = IntStream.of(pixels).parallel().filter(color -> color == Color.BLACK.getRGB()).count(); + + boolean isBlack = blackPixelCount < blackPixelThreshold; + if (!isBlack) { + LOGGER.info("Image is predominantly black. Retrying..."); + } + return isBlack; + } + private double[] rotatePoint(double x, double y, double angle) { double cosAngle = Math.cos(angle); double sinAngle = Math.sin(angle); diff --git a/src/main/java/de/fractalitylab/generators/JuliaGenerator.java b/src/main/java/de/fractalitylab/generators/JuliaGenerator.java index d872095..91c0c1e 100644 --- a/src/main/java/de/fractalitylab/generators/JuliaGenerator.java +++ b/src/main/java/de/fractalitylab/generators/JuliaGenerator.java @@ -1,9 +1,8 @@ package de.fractalitylab.generators; +import de.fractalitylab.FractalityLab; import de.fractalitylab.data.ImageWriter; import de.fractalitylab.data.DataElement; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; import java.awt.*; import java.awt.image.BufferedImage; @@ -11,10 +10,12 @@ import java.util.List; import java.util.UUID; import java.util.concurrent.ThreadLocalRandom; +import java.util.logging.Logger; import java.util.stream.IntStream; public class JuliaGenerator implements ImageGenerator { - private static final Logger LOGGER = LogManager.getLogger(JuliaGenerator.class); + private static final Logger LOGGER = Logger.getLogger(JuliaGenerator.class.getName()); + ThreadLocalRandom random = ThreadLocalRandom.current(); @Override @@ -23,7 +24,6 @@ public List generateImage(int width, int height, int maxIterations IntStream.range(1, numberOfImages + 1).parallel().forEach(imageNumber -> { BufferedImage image; do { - LOGGER.info("Generating image " + imageNumber + "..."); image = generateSingleImage(width, height, maxIterations, imageNumber); } while (!isImageInteresting(image, 3, 10)); @@ -31,11 +31,11 @@ public List generateImage(int width, int height, int maxIterations ImageWriter.writeImage("julia", uuid.toString(), image); result.add(new DataElement(uuid.toString(), "julia")); }); + LOGGER.info("Julia generation finished."); return result; } private BufferedImage generateSingleImage(int width, int height, int maxIterations, int imageNumber) { - LOGGER.info("Generating image..."); BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); ThreadLocalRandom random = ThreadLocalRandom.current(); diff --git a/src/main/java/de/fractalitylab/generators/MandelbrotGenerator.java b/src/main/java/de/fractalitylab/generators/MandelbrotGenerator.java index a89fa24..ca60859 100644 --- a/src/main/java/de/fractalitylab/generators/MandelbrotGenerator.java +++ b/src/main/java/de/fractalitylab/generators/MandelbrotGenerator.java @@ -1,65 +1,59 @@ package de.fractalitylab.generators; -import de.fractalitylab.data.ImageWriter; import de.fractalitylab.data.DataElement; +import de.fractalitylab.data.ImageWriter; import java.awt.*; +import java.awt.image.BufferedImage; import java.util.ArrayList; import java.util.List; -import java.awt.image.BufferedImage; import java.util.UUID; import java.util.concurrent.ThreadLocalRandom; +import java.util.logging.Logger; import java.util.stream.IntStream; public class MandelbrotGenerator implements ImageGenerator { + private static final Logger LOGGER = Logger.getLogger(MandelbrotGenerator.class.getName()); + ThreadLocalRandom random = ThreadLocalRandom.current(); @Override public List generateImage(int width, int height, int maxIterations, int numberOfImages) { List result = new ArrayList<>(); int iterations = maxIterations * 100; - IntStream.range(1, numberOfImages + 1).parallel().forEach(imageNumber -> { + IntStream.range(1, numberOfImages + 1).forEach(imageNumber -> { BufferedImage image; - do { - image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); - double zoom = 1000.0 + random.nextDouble() * 10000.0; - double moveX = -0.7 + random.nextDouble() * 0.7; - double moveY = random.nextDouble() * 0.7; - BufferedImage finalImage = image; - IntStream.range(0, height).parallel().forEach(y -> { - for (int x = 0; x < width; x++) { - double zx = (x - width / 2.0) / zoom + moveX; - double zy = (y - height / 2.0) / zoom + moveY; - double cX = zx; - double cY = zy; - int iter = 0; - double tmp; - while ((zx * zx + zy * zy < 4) && (iter < iterations)) { - tmp = zx * zx - zy * zy + cX; - zy = 2.0 * zx * zy + cY; - zx = tmp; - iter++; - } - int color = Color.HSBtoRGB((float) iter / iterations + (iter % 2) * 0.5f, 1, iter < iterations ? 1 : 0); - finalImage.setRGB(x, y, color); + image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); + double zoom = 1000.0 + random.nextDouble() * 10000.0; + double moveX = -0.7 + random.nextDouble() * 0.7; + double moveY = random.nextDouble() * 0.7; + BufferedImage finalImage = image; + IntStream.range(0, height).parallel().forEach(y -> { + for (int x = 0; x < width; x++) { + double zx = (x - width / 2.0) / zoom + moveX; + double zy = (y - height / 2.0) / zoom + moveY; + double cX = zx; + double cY = zy; + int iter = 0; + double tmp; + while ((zx * zx + zy * zy < 4) && (iter < iterations)) { + tmp = zx * zx - zy * zy + cX; + zy = 2.0 * zx * zy + cY; + zx = tmp; + iter++; } - }); - } while (isImageAllBlack(image)); + int color = Color.HSBtoRGB((float) iter / iterations + (iter % 2) * 0.5f, 1, iter < iterations ? 1 : 0); + finalImage.setRGB(x, y, color); + } + }); UUID uuid = UUID.randomUUID(); ImageWriter.writeImage("mandelbrot", uuid.toString(), image); result.add(new DataElement(uuid.toString(), "mandelbrot")); }); + + LOGGER.info("Mandelbrot generation finished."); + return result; } - private boolean isImageAllBlack(BufferedImage image) { - for (int y = 0; y < image.getHeight(); y++) { - for (int x = 0; x < image.getWidth(); x++) { - if (image.getRGB(x, y) != Color.BLACK.getRGB()) { - return false; - } - } - } - return true; - } } diff --git a/src/main/java/de/fractalitylab/generators/NewtonFractalGenerator.java b/src/main/java/de/fractalitylab/generators/NewtonFractalGenerator.java new file mode 100644 index 0000000..aae3c8a --- /dev/null +++ b/src/main/java/de/fractalitylab/generators/NewtonFractalGenerator.java @@ -0,0 +1,201 @@ +package de.fractalitylab.generators; + +import de.fractalitylab.data.DataElement; +import de.fractalitylab.data.ImageWriter; + +import java.awt.*; +import java.awt.image.BufferedImage; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; +import java.util.concurrent.ThreadLocalRandom; +import java.util.logging.Logger; +import java.util.stream.IntStream; + +public class NewtonFractalGenerator implements ImageGenerator { + private static final Logger LOGGER = Logger.getLogger(NewtonFractalGenerator.class.getName()); + + ThreadLocalRandom random = ThreadLocalRandom.current(); + + + public NewtonFractalGenerator() { + } + + public BufferedImage generateImage(int width, int height, int quality) { + BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); + Graphics2D g = image.createGraphics(); + + // Parameters for fractal generation + double zoom = 1 + 1000 * random.nextDouble(); // Random zoom + double rotation = 2 * Math.PI * random.nextDouble(); // Random rotation + + // Generate fractal (this is a placeholder for the actual fractal generation logic) + for (int x = 0; x < width; x++) { + for (int y = 0; y < height; y++) { + // Apply rotation and zoom to the current point + double nx = (x - width / 2) / zoom; + double ny = (y - height / 2) / zoom; + double angle = Math.atan2(ny, nx) + rotation; + double radius = Math.sqrt(nx * nx + ny * ny); + nx = radius * Math.cos(angle); + ny = radius * Math.sin(angle); + + // Determine the color of the point based on the Newton iteration (placeholder) + int color = getColorFromIteration(nx, ny, quality); + image.setRGB(x, y, color); + } + } + + g.dispose(); + return image; + } + + @Override + public List generateImage(int width, int height, int maxIterations, int numberOfImages) { + List result = new ArrayList<>(); + IntStream.range(1, numberOfImages + 1).parallel().forEach(imageNumber -> { + BufferedImage image; + image = generateImage(width, height, maxIterations*random.nextInt(1, 300)); + UUID uuid = UUID.randomUUID(); + ImageWriter.writeImage("newton", uuid.toString(), image); + }); + + LOGGER.info("Newton generation finished."); + return result; + } + + private int getColorFromIteration(double x, double y, int quality) { + final int MAX_ITERATIONS = quality; + final double CONVERGENCE_THRESHOLD = 1e-6; + + // Wurzeln des Polynoms z^3 - 1 + Complex[] roots = { + new Complex(1, 0), + new Complex(-0.5, Math.sqrt(3) / 2), + new Complex(-0.5, -Math.sqrt(3) / 2) + }; + // Farben für die Wurzeln + Color[] colors = { + new Color(255, 0, 0), // Rot + new Color(0, 255, 0), // Grün + new Color(0, 0, 255) // Blau + }; + + Complex z = new Complex(x, y); + Complex zPrev; + + int iterations = 0; + double minDistance = Double.MAX_VALUE; + int closestRootIndex = -1; + do { + zPrev = z; + z = z.subtract(f(z).divide(fPrime(z))); + + // Finde die nächste Wurzel und ihre Distanz + for (int i = 0; i < roots.length; i++) { + double distance = z.subtract(roots[i]).modulus(); + if (distance < minDistance) { + minDistance = distance; + closestRootIndex = i; + } + } + + if (minDistance < CONVERGENCE_THRESHOLD) { + break; + } + + iterations++; + } while (iterations < MAX_ITERATIONS); + + // Falls keine Konvergenz erreicht wurde, setzen wir die Farbe auf Schwarz. + if (closestRootIndex == -1) { + return 0x000000; + } + + // Erzeuge Farbübergänge zwischen den Wurzeln + float[] colorWeights = new float[roots.length]; + for (int i = 0; i < roots.length; i++) { + if (i == closestRootIndex) { + colorWeights[i] = 1.0f - (float)iterations / MAX_ITERATIONS; + } else { + colorWeights[i] = 0.0f; + } + } + + // Berechne die neue Farbe + int r = 0, g = 0, b = 0; + for (int i = 0; i < roots.length; i++) { + r += colorWeights[i] * colors[i].getRed(); + g += colorWeights[i] * colors[i].getGreen(); + b += colorWeights[i] * colors[i].getBlue(); + } + + // Anpassung für mehr Helligkeit + float brightnessAdjustment = (float) (1.0f - ((float)minDistance / CONVERGENCE_THRESHOLD)); + float colorAdjustment = 1.0f + brightnessAdjustment; + r = (int)(r * colorAdjustment*brightnessAdjustment) % 256; + g = (int)(g * colorAdjustment*brightnessAdjustment) % 256; + b = (int)(b * colorAdjustment*brightnessAdjustment) % 256; + + return new Color(r, g, b).getRGB(); + } + + + + // Die Funktion f(z) für die Newton-Raphson-Iteration + private Complex f(Complex z) { + // Beispiel für ein Polynom: z^3 - 1 + return z.pow(3).subtract(new Complex(1, 0)); + } + + // Die Ableitung von f(z) + private Complex fPrime(Complex z) { + // Die Ableitung von z^3 - 1 ist 3z^2 + return z.pow(2).multiply(new Complex(3, 0)); + } + + // Einfache komplexe Zahl Klasse + class Complex { + private double re; + private double im; + + public Complex(double real, double imaginary) { + this.re = real; + this.im = imaginary; + } + + public Complex subtract(Complex b) { + return new Complex(this.re - b.re, this.im - b.im); + } + + public Complex multiply(Complex b) { + return new Complex(this.re * b.re - this.im * b.im, this.re * b.im + this.im * b.re); + } + + public Complex divide(Complex b) { + Complex conjugate = b.conjugate(); + Complex numerator = this.multiply(conjugate); + double denominator = b.re * b.re + b.im * b.im; + return new Complex(numerator.re / denominator, numerator.im / denominator); + } + + public Complex pow(int power) { + Complex result = this; + for (int i = 1; i < power; i++) { + result = result.multiply(this); + } + return result; + } + + public double modulus() { + return Math.sqrt(re * re + im * im); + } + + public Complex conjugate() { + return new Complex(re, -im); + } + + } + + +} diff --git a/src/main/java/de/fractalitylab/generators/SierpinskiGasketGenerator.java b/src/main/java/de/fractalitylab/generators/SierpinskiGasketGenerator.java index f53bf29..9c15a56 100644 --- a/src/main/java/de/fractalitylab/generators/SierpinskiGasketGenerator.java +++ b/src/main/java/de/fractalitylab/generators/SierpinskiGasketGenerator.java @@ -9,9 +9,10 @@ import java.util.List; import java.util.Random; import java.util.UUID; +import java.util.logging.Logger; public class SierpinskiGasketGenerator implements ImageGenerator { - + private static final Logger LOGGER = Logger.getLogger(SierpinskiGasketGenerator.class.getName()); private final Random rand = new Random(); @Override @@ -40,6 +41,9 @@ public List generateImage(int width, int height, int maxIterations, ImageWriter.writeImage("sierpinski_gasket", uuid.toString(), image); result.add(new DataElement(uuid.toString(), "sierpinski_gasket")); } + + LOGGER.info("SierpinskiGasket generation finished."); + return result; } diff --git a/src/main/java/de/fractalitylab/generators/TricornGenerator.java b/src/main/java/de/fractalitylab/generators/TricornGenerator.java index 5e70870..52e1946 100644 --- a/src/main/java/de/fractalitylab/generators/TricornGenerator.java +++ b/src/main/java/de/fractalitylab/generators/TricornGenerator.java @@ -1,5 +1,6 @@ package de.fractalitylab.generators; +import de.fractalitylab.FractalityLab; import de.fractalitylab.data.ImageWriter; import de.fractalitylab.data.DataElement; @@ -9,8 +10,10 @@ import java.util.List; import java.util.Random; import java.util.UUID; +import java.util.logging.Logger; public class TricornGenerator implements ImageGenerator { + private static final Logger LOGGER = Logger.getLogger(TricornGenerator.class.getName()); private Random random = new Random(); @Override @@ -38,6 +41,7 @@ public List generateImage(int width, int height, int maxIterations, ImageWriter.writeImage("tricorn", uuid.toString(), image); dataElements.add(new DataElement(uuid.toString(), "tricorn")); } + LOGGER.info("Tricorn generation finished."); return dataElements; }