Skip to content
Snippets Groups Projects
Commit 9f2f8260 authored by LABOUREL Arnaud's avatar LABOUREL Arnaud
Browse files

New version with hamcrest

parent 44d01dad
No related branches found
No related tags found
No related merge requests found
......@@ -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
rootProject.name = 'students'
rootProject.name = 'mandelbrot'
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;
}
......
......@@ -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 -&gt; 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;
......
......@@ -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 {
}
......
......@@ -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
*/
......
......@@ -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));
......
......@@ -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]);
......
......@@ -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();
......
......@@ -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);
}
......
......@@ -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
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment