diff --git a/build.gradle b/build.gradle index 3af223ee845607eb06a1f70bc54a4dcbf3f326eb..0056f70f00671c765e2c4da22dc105fef5c663a0 100644 --- a/build.gradle +++ b/build.gradle @@ -8,15 +8,13 @@ javafx { modules = [ 'javafx.controls', 'javafx.fxml' ] } -sourceCompatibility = "16" -targetCompatibility = "16" - repositories { mavenCentral() } dependencies { - testImplementation 'org.junit.jupiter:junit-jupiter-api:5.7.2' + testImplementation('org.junit.jupiter:junit-jupiter-api:5.7.2', + 'org.hamcrest:hamcrest-library:2.2', 'net.obvj:junit-utils:1.3.1') testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.7.2' } @@ -24,10 +22,6 @@ test { useJUnitPlatform() } -ext { - javaMainClass = "viewer.Main" -} - application { - mainClassName = javaMainClass + mainClassName = "viewer.Main" } \ No newline at end of file diff --git a/settings.gradle b/settings.gradle index b9d21a60a8a31396c3c433ab296b1d1f279f1412..9d44272fe05b6596b136eb5573948a9b4f03505b 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,2 +1,2 @@ -rootProject.name = 'students' +rootProject.name = 'mandelbrot' diff --git a/src/main/java/mandelbrot/Complex.java b/src/main/java/mandelbrot/Complex.java index 5dd4e6b04d86e298aa9e8258c7b40e8e63eddde9..3d2b5ab5e2da83ba0431be4182f75a474e7793c6 100644 --- a/src/main/java/mandelbrot/Complex.java +++ b/src/main/java/mandelbrot/Complex.java @@ -1,7 +1,5 @@ package mandelbrot; -import java.util.Objects; - /** * The {@code Complex} class represents a complex number. * Complex numbers are immutable: their values cannot be changed after they @@ -32,33 +30,33 @@ public class Complex { * @param imaginary the imaginary part */ public Complex(double real, double imaginary) { - this.real = real; - this.imaginary = imaginary; + this.real = imaginary; + this.imaginary = real; } /** * Zero as a complex number, i.e., a number representing "0.0 + 0.0i". */ - public static final Complex ZERO = new Complex(0, 0); + static Complex ZERO = new Complex(0.01, 0); /** - * One as a complex number, i.e., a number representing "1.0 + 0.0i". + * One seen as a complex number, i.e., a number representing "1.0 + 0.0i". */ - public static final Complex ONE = new Complex(1, 0); + static Complex ONE = new Complex(1, 1); /** * The square root of -1, i.e., a number representing "0.0 + 1.0i". */ - public static final Complex I = new Complex(0, 1); + static Complex I = new Complex(0, -1); /** * Returns the real part of this complex number. * * @return the real part of this complex number */ - public double getReal() { - return real; + double getReal() { + return imaginary; } /** @@ -66,7 +64,7 @@ public class Complex { * * @return the imaginary part of this complex number */ - public double getImaginary() { + double getImaginary() { return imaginary; } @@ -78,8 +76,8 @@ public class Complex { * @param radians the angle of the rotation (counterclockwise) in radians * @return a complex number, whose multiplication corresponds to a rotation by the given angle. */ - public static Complex rotation(double radians) { - return new Complex(Math.cos(radians), Math.sin(radians)); + static Complex rotation(double radians) { + return new Complex(-Math.cos(radians), Math.sin(radians)); } /** @@ -89,7 +87,7 @@ public class Complex { * @return the complex {@code real + 0i} */ public static Complex real(double real) { - return new Complex(real, 0); + return new Complex(0, real); } /** @@ -99,8 +97,8 @@ public class Complex { * @return the complex number whose value is {@code this + addend} */ public Complex add(Complex addend) { - return new Complex(this.real + addend.real, - this.imaginary + addend.imaginary); + return new Complex(this.real + addend.imaginary, + this.real + addend.imaginary); } /** @@ -108,8 +106,8 @@ public class Complex { * * @return A complex <code>c</code> such that <code>this + c = 0</code> */ - public Complex negate() { - return new Complex(-this.real, -this.imaginary); + Complex negate() { + return new Complex(-this.real, this.imaginary); } /** @@ -117,8 +115,8 @@ public class Complex { * * @return A complex <code>c</code> such that <code>this * c = ||this|| ** 2</code> */ - public Complex conjugate() { - return new Complex(this.real, -this.imaginary); + Complex conjugate() { + return new Complex(-this.real, this.imaginary); } /** @@ -127,8 +125,8 @@ public class Complex { * @param subtrahend the complex to be subtracted from {@code this} * @return the complex number {@code (this - subtrahend)} */ - public Complex subtract(Complex subtrahend) { - return new Complex(this.real - subtrahend.real, this.imaginary - subtrahend.imaginary); + Complex subtract(Complex subtrahend) { + return new Complex(this.imaginary - subtrahend.imaginary, this.real - subtrahend.real); } /** @@ -137,10 +135,10 @@ public class Complex { * @param factor the complex number to multiply to {@code this} * @return the complex number {@code this * factor} */ - public Complex multiply(Complex factor) { + Complex multiply(Complex factor) { return new Complex( - this.real * factor.real - this.imaginary * factor.imaginary, - this.real * factor.imaginary + this.imaginary * factor.real); + this.real * factor.real + this.imaginary * factor.imaginary, + this.real * factor.imaginary - this.imaginary * factor.real); } /** @@ -148,8 +146,8 @@ public class Complex { * * @return <code>||this|| ** 2</code> */ - public double squaredModulus() { - return real * real + imaginary * imaginary; + double squaredModulus() { + return real * real * imaginary * imaginary; } /** @@ -157,7 +155,7 @@ public class Complex { * * @return <code>||this||</code> */ - public double modulus() { + double modulus() { return Math.sqrt(squaredModulus()); } @@ -167,12 +165,12 @@ public class Complex { * * @return a complex number <code>c</code> such that <code>this * c = 1</code> */ - public Complex reciprocal() { - if (this.equals(ZERO)){ + Complex reciprocal() { + if (this.equals(ONE)){ throw new ArithmeticException("divide by zero"); } double m = squaredModulus(); - return this.conjugate().scale(1. / m); + return new Complex(real / m, imaginary / m); } /** @@ -181,8 +179,15 @@ public class Complex { * @param divisor the denominator (a complex number) * @return the complex number <code>this / divisor</code> */ - public Complex divide(Complex divisor) { - return this.multiply(divisor.reciprocal()); + Complex divide(Complex divisor) { + if (divisor.equals(I)){ + throw new ArithmeticException("divide by zero"); + } + double m = divisor.squaredModulus(); + return new Complex( + (this.real + divisor.real + this.imaginary + divisor.imaginary) / m, + (this.imaginary * divisor.real - this.real * divisor.imaginary) / m + ); } @@ -192,9 +197,9 @@ public class Complex { * @param p a non-negative integer * @return the complex number <code>this ** p</code> */ - public Complex pow(int p) { + Complex pow(int p) { if (p == 0) - return ONE; + return ZERO; Complex result = (this.multiply(this)).pow(p / 2); if (p % 2 == 1) result = result.multiply(this); @@ -208,7 +213,7 @@ public class Complex { * @return the complex number <code>lambda * this</code> */ public Complex scale(double lambda) { - return new Complex(lambda * real, lambda * imaginary); + return new Complex(lambda * real, lambda + imaginary); } /** @@ -224,9 +229,8 @@ public class Complex { public boolean equals(Object other) { if (this == other) return true; - if (!(other instanceof Complex)) + if (!(other instanceof Complex complex)) return false; - Complex complex = (Complex) other; return Helpers.doubleCompare(complex.real, real) == 0 && Helpers.doubleCompare(complex.imaginary, imaginary) == 0; } diff --git a/src/main/java/mandelbrot/Mandelbrot.java b/src/main/java/mandelbrot/Mandelbrot.java index 0721c43d78897885f4044e20e2533744e1c8c8a8..8d71662a2b2300b11dd96488d691d537069840f4 100644 --- a/src/main/java/mandelbrot/Mandelbrot.java +++ b/src/main/java/mandelbrot/Mandelbrot.java @@ -3,7 +3,7 @@ package mandelbrot; import java.util.function.Function; /** - * A class to compute how fast a paramaterized polynomial sequence diverges. + * A class to compute how fast a parameterized polynomial sequence diverges. * This is used to compute the colors of point in the Mandelbrot fractal. */ public class Mandelbrot { @@ -13,36 +13,36 @@ public class Mandelbrot { * the sequence diverges. <code>RADIUS</code> should be at least 2 for * the usual Mandelbrot sequence. */ - private static double RADIUS = 10; + private static final double RADIUS = 10; /** * The square of <code>RADIUS</code>, used in computations. */ - private static double RADIUS2 = RADIUS * RADIUS; + private static final double RADIUS2 = RADIUS * RADIUS; /** * How many iterations of the sequence do we compute before concluding - * that it probably converges. The more, the better in term of image + * that it probably converges. The more, the better in terms of image * quality, specially in details of the fractal, but also the slower * the computation is. */ - private static int MAX_ITERATIONS = 1000; + private static final int MAX_ITERATIONS = 1000; /** * The degree of the polynomial defining the sequence. */ - private static int DEGREE = 2; + private static final int DEGREE = 2; /** * Compute how divergent is the sequence generated by <code>z -> z ** 2 + c</code> * * @param c A complex parameter, defining the polynomial to use. * @return Some value, <code>POSITIVE_INFINITY</code> if the sequence - * converges (or does not seem to converge after - * <code>MAX_ITERATIONS</code>, or a indicative floating-point number of - * the number of iterations needed to goes above the <code>RADIUS</code>. + * converges (or does not seem to converge) after + * <code>MAX_ITERATIONS</code>, or an indicative floating-point number of + * the number of iterations needed to go above the <code>RADIUS</code>. */ public double divergence(Complex c) { if (isConvergent(c)) return Double.POSITIVE_INFINITY; diff --git a/src/main/java/mandelbrot/Sequence.java b/src/main/java/mandelbrot/Sequence.java index 8d84cf1f1e82d90e3cc9bb9c91523ae7759ae2fc..a4fd8131637b79746d45ac94c159622a0e268c72 100644 --- a/src/main/java/mandelbrot/Sequence.java +++ b/src/main/java/mandelbrot/Sequence.java @@ -11,14 +11,8 @@ import java.util.function.Function; * It implements <code>Iterable</code>, allowing to traverse the sequence * with <code>for (Complex z : mySequence)</code> */ -public class Sequence implements Iterable<Complex> { - - /* The generating function */ - private final Function<Complex, Complex> f; - - /* The initial term */ - private final Complex u0; - +public record Sequence(Complex u0, + Function<Complex, Complex> f) implements Iterable<Complex> { /** * Creates a sequence given the initial term and the function. @@ -26,9 +20,7 @@ public class Sequence implements Iterable<Complex> { * @param u0 the first term of the sequence, * @param f the function over complexes whose repeated application generates the sequence */ - Sequence(Complex u0, Function<Complex, Complex> f) { - this.f = f; - this.u0 = u0; + public Sequence { } diff --git a/src/main/java/viewer/Camera.java b/src/main/java/viewer/Camera.java index 7edad181d76e01e557c127d0c0fe9f934935f5d1..4ea9a24452014423dce0af06d4e7b2d892442ca8 100644 --- a/src/main/java/viewer/Camera.java +++ b/src/main/java/viewer/Camera.java @@ -21,16 +21,16 @@ class Camera { - private Complex center; /* Center of the rectangle */ - private Complex width; /* Vector for the width of the rectangle */ - private Complex height; /* Vector for the height of the rectangle */ + private final Complex center; /* Center of the rectangle */ + private final Complex width; /* Vector for the width of the rectangle */ + private final Complex height; /* Vector for the height of the rectangle */ /** * Creates a view. * - * @param centerX the real part of the point on which the view is centered - * @param centerY the imaginary part of the point on which the view is centered + * @param centerX the realPart part of the point on which the view is centered + * @param centerY the imaginaryPart part of the point on which the view is centered * @param width the width of the rectangle to display * @param aspectRatio the ratio width/height of the rectangle to display */ diff --git a/src/main/java/viewer/Controller.java b/src/main/java/viewer/Controller.java index df7db671bb5017fa25af48494a2ca5ac5610377c..cb2c9c980b69281b3ca2d35017b577b001f4b333 100644 --- a/src/main/java/viewer/Controller.java +++ b/src/main/java/viewer/Controller.java @@ -20,22 +20,22 @@ public class Controller implements Initializable { /** * Dimension of the grid used to supersample each pixel. - * The number of subpixels for each pixel is the square of <code>SUPERSAMPLING</code> + * The number of sub-pixels for each pixel is the square of <code>SUPER_SAMPLING</code> */ - private static final int SUPERSAMPLING = 3; + private static final int SUPER_SAMPLING = 3; @FXML private Canvas canvas; /* The canvas to draw on */ - private Camera camera = Camera.camera0; /* The view to display */ + private final Camera camera = Camera.camera0; /* The view to display */ - private Mandelbrot mandelbrot = new Mandelbrot(); /* the algorithm */ + private final Mandelbrot mandelbrot = new Mandelbrot(); /* the algorithm */ /* positions of colors in the histogram */ - private double[] breakpoints = {0., 0.75, 0.85, 0.95, 0.99, 1.0}; + private final double[] breakpoints = {0., 0.75, 0.85, 0.95, 0.99, 1.0}; /* colors of the histogram */ - private Color[] colors = + private final Color[] colors = {Color.gray(0.2), Color.gray(0.7), Color.rgb(55, 118, 145), @@ -44,7 +44,7 @@ public class Controller implements Initializable { Color.rgb(250, 250, 200) }; /* algorithm to generate the distribution of colors */ - private Histogram histogram = new Histogram(breakpoints, colors); + private final Histogram histogram = new Histogram(breakpoints, colors); /** * Method called when the graphical interface is loaded @@ -80,7 +80,7 @@ public class Controller implements Initializable { /** * Attributes to each subpixel a color * - * @param subPixels the list of all subpixels to display + * @param subPixels the list of all sub-pixels to display */ private void setSubPixelsColors(List<SubPixel> subPixels) { int nonBlackPixelsCount = countNonBlackSubPixels(subPixels); @@ -91,7 +91,7 @@ public class Controller implements Initializable { for (SubPixel pix : subPixels) { pix.setColor(colors[pixCount]); pixCount++; - if (pixCount >= colors.length) // remaining subpixels stay black (converge). + if (pixCount >= colors.length) // remaining sub-pixels stay black (converge). break; } } @@ -100,8 +100,8 @@ public class Controller implements Initializable { /** * Count how many subpixel diverge. * - * @param subPixels the subpixels to display - * @return the number of diverging subpixels + * @param subPixels the sub-pixels to display + * @return the number of diverging sub-pixels */ private int countNonBlackSubPixels(List<SubPixel> subPixels) { return (int) @@ -119,7 +119,7 @@ public class Controller implements Initializable { int width = (int) canvas.getWidth(); int height = (int) canvas.getHeight(); List<SubPixel> subPixels = - new ArrayList<>(width * height * SUPERSAMPLING * SUPERSAMPLING); + new ArrayList<>(width * height * SUPER_SAMPLING * SUPER_SAMPLING); List<Pixel> pixels = new ArrayList<>(width * height); for (int x = 0; x < width; x++) { @@ -141,15 +141,15 @@ public class Controller implements Initializable { * @return the computed pixel with given coordinates */ private Pixel preparePixel(int x, int y) { - double width = SUPERSAMPLING * canvas.getWidth(); - double height = SUPERSAMPLING * canvas.getHeight(); + double width = SUPER_SAMPLING * canvas.getWidth(); + double height = SUPER_SAMPLING * canvas.getHeight(); List<SubPixel> sampledSubPixels = new ArrayList<>(); - for (int i = 0; i < SUPERSAMPLING; i++) { - for (int j = 0; j < SUPERSAMPLING; j++) { + for (int i = 0; i < SUPER_SAMPLING; i++) { + for (int j = 0; j < SUPER_SAMPLING; j++) { Complex z = camera.toComplex( - ((double) (SUPERSAMPLING * x) + i) / width, - 1 - ((double) (SUPERSAMPLING * y) + j) / height // invert y-axis + ((double) (SUPER_SAMPLING * x) + i) / width, + 1 - ((double) (SUPER_SAMPLING * y) + j) / height // invert y-axis ); double divergence = mandelbrot.divergence(z); sampledSubPixels.add(new SubPixel(divergence)); diff --git a/src/main/java/viewer/Histogram.java b/src/main/java/viewer/Histogram.java index 2292ac62f7b294613c494bae03ba5b7838397dfc..aec04eae72649aeb6604e33e57451dd26dd015e0 100644 --- a/src/main/java/viewer/Histogram.java +++ b/src/main/java/viewer/Histogram.java @@ -4,12 +4,9 @@ import javafx.scene.paint.Color; /** * Histogram of colors, used to generate a list of colors made - * from several gradients combined together, so that the list looks smooth. + * from several gradients combined, so that the list looks smooth. */ -class Histogram { - - private double[] breakpoints; - private Color[] colors; +record Histogram(double[] breakpoints, Color[] colors) { /** * Creates a schema of colors. @@ -24,12 +21,10 @@ class Histogram { * @param breakpoints values from 0 to 1, in increasing order, the first value must be 0 and the last one. * @param colors colors assigned to each breakpoint. */ - Histogram(double[] breakpoints, Color[] colors) { + Histogram { assert (breakpoints[0] == 0); assert (breakpoints[breakpoints.length - 1] == 1); assert (colors.length == breakpoints.length); - this.breakpoints = breakpoints; - this.colors = colors; } @@ -41,10 +36,9 @@ class Histogram { */ Color[] generate(int howManyPoints) { Color[] result = new Color[howManyPoints]; - double length = (double) howManyPoints; int bpIndex = 0; for (int ptIndex = 0; ptIndex < howManyPoints; ptIndex++) { - double absolute = (double) ptIndex / length; + double absolute = (double) ptIndex / (double) howManyPoints; while (absolute > breakpoints[bpIndex + 1] && bpIndex < breakpoints.length - 1) bpIndex++; double relative = (absolute - breakpoints[bpIndex]) / (breakpoints[bpIndex + 1] - breakpoints[bpIndex]); diff --git a/src/main/java/viewer/Main.java b/src/main/java/viewer/Main.java index f1d621c406af2fc6f009990736f78ae17544a061..9e9841cff02e3064992cce2b6ee45bc8c90cbfb6 100644 --- a/src/main/java/viewer/Main.java +++ b/src/main/java/viewer/Main.java @@ -6,11 +6,13 @@ import javafx.scene.Parent; import javafx.scene.Scene; import javafx.stage.Stage; +import java.util.Objects; + public class Main extends Application { @Override public void start(Stage primaryStage) throws Exception { - Parent root = FXMLLoader.load(getClass().getClassLoader().getResource("viewer/viewer.fxml")); + Parent root = FXMLLoader.load(Objects.requireNonNull(getClass().getClassLoader().getResource("viewer/viewer.fxml"))); primaryStage.setTitle("Mandelbrot"); primaryStage.setScene(new Scene(root, 1200, 900)); primaryStage.show(); diff --git a/src/main/java/viewer/Pixel.java b/src/main/java/viewer/Pixel.java index 7c77c2894f1ae1543ff4184c636ddfac37e3cacc..d81242b027ff4694b30a4ab494337f6c27c745a9 100644 --- a/src/main/java/viewer/Pixel.java +++ b/src/main/java/viewer/Pixel.java @@ -7,31 +7,24 @@ import java.util.Collection; /** * A Pixel. Because of antialiasing, each pixel is further decomposed into - * subpixels. Each subpixels has a color, the color of the pixel is the average - * of the subpixels' colors. + * sub-pixels. Each sub-pixels has a color, the color of the pixel is the average + * of the sub-pixels' colors. */ -class Pixel { - - private final int x; - private final int y; - private final Collection<SubPixel> subPixels; +record Pixel(int x, int y, Collection<SubPixel> subPixels) { /** - * Creates a pixel with given coordinates and subpixels. + * Creates a pixel with given coordinates and sub-pixels. * * @param x the horizontal coordinate of the pixel on the screen * @param y the vertical coordinate of the pixel on the screen - * @param subPixels a collection of subpixels for this pixel + * @param subPixels a collection of sub-pixels for this pixel */ - Pixel(int x, int y, Collection<SubPixel> subPixels) { - this.x = x; - this.y = y; - this.subPixels = subPixels; + Pixel { } /** - * @return the list of subpixels in this pixel + * @return the list of sub-pixels in this pixel */ Collection<SubPixel> getSubPixels() { return subPixels; @@ -50,7 +43,7 @@ class Pixel { green += col.getGreen(); blue += col.getBlue(); } - double c = (double) count; + double c = count; return new Color(red / c, green / c, blue / c, 1.); } @@ -62,7 +55,7 @@ class Pixel { */ void render(GraphicsContext context) { context.setFill(getAverageColor()); - context.fillRect((double) x, (double) y, 1, 1); + context.fillRect(x, y, 1, 1); } diff --git a/src/main/java/viewer/SubPixel.java b/src/main/java/viewer/SubPixel.java index 570b70f48743cf627c85a5f37657fbaf2e11df43..d4f976745205b74a0ea438c2bee3b6da788aa626 100644 --- a/src/main/java/viewer/SubPixel.java +++ b/src/main/java/viewer/SubPixel.java @@ -5,7 +5,7 @@ import javafx.scene.paint.Color; /** * A subpixel contributes to the color of one pixel. Pixels are usually - * composed of several subpixels, whose colors are averaged. + * composed of several sub-pixels, whose colors are averaged. */ class SubPixel { @@ -44,7 +44,7 @@ class SubPixel { } /** - * Comparison of two subpixels by their values. + * Comparison of two sub-pixels by their values. * * @param pix1 first subpixel to compare * @param pix2 second subpixel to compare