diff --git a/README.md b/README.md
index 620603fa79b24938a364b4bd46f34f8168f372c0..7eb4fa57b39a8251373f527077a7de69f24cf402 100644
--- a/README.md
+++ b/README.md
@@ -1,8 +1,9 @@
-# Gestion des notes des étudiants
+# Mandelbrot
-## Description du projet
+## Description du projet
-Le but de ce TP est de créer des classes permettant de représenter des étudiants (classe `Student`), des notes (classe `Grade`), des résultats à une unité d'enseignement (classe `TeachingUnitResult`) et des promotions d'étudiants (classe `Cohort`).
+On va travailler sur ce TP sur l'affichage de l'[ensemble de Mandelbrot](https://en.wikipedia.org/wiki/Mandelbrot_set). Pour cela, on va utiliser un code pré-existant.
+Le but du TP sera de corriger le code de la classe `Complex` en s'aidant de tests unitaires.
## Membres du projet
diff --git a/build.gradle b/build.gradle
index b2184c7b55f18065361b00f429f9cf48647eef62..3af223ee845607eb06a1f70bc54a4dcbf3f326eb 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1,10 +1,15 @@
plugins {
- id "application"
+ id 'application'
+ id "org.openjfx.javafxplugin" version "0.0.10"
}
-apply plugin : "java"
-group 'l2info'
-version '1.0-SNAPSHOT'
+javafx {
+ version = "17"
+ modules = [ 'javafx.controls', 'javafx.fxml' ]
+}
+
+sourceCompatibility = "16"
+targetCompatibility = "16"
repositories {
mavenCentral()
@@ -20,7 +25,7 @@ test {
}
ext {
- javaMainClass = "Main"
+ javaMainClass = "viewer.Main"
}
application {
diff --git a/src/main/java/Cohort.java b/src/main/java/Cohort.java
deleted file mode 100644
index b997326978841098c0235774f90bd70fd6c389c1..0000000000000000000000000000000000000000
--- a/src/main/java/Cohort.java
+++ /dev/null
@@ -1,56 +0,0 @@
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * A group of students.
- */
-
-public class Cohort {
- private final String name;
- private final List<Student> students;
-
- /**
- * Constructs a cohort with a name equals to the specified {@code name} and no students.
- * @param name the name of the constructed Cohort
- */
-
- public Cohort(String name) {
- this.name = name;
- this.students = new ArrayList<>();
- }
-
- /**
- * Add the specified {@code student} to the students of the cohort.
- * @param student the student to be added to the cohort
- */
- public void addStudent(Student student){
- // TODO : add code
- }
-
- /**
- * Returns the list of students of the cohort.
- * @return the list of students of the cohort.
- */
- public List<Student> getStudents(){
- // TODO : change code
- return null;
- }
-
- /**
- * Print via the standard output the name of the cohort and all results associated to the students with their average
- * grade.
- */
- public void printStudentsResults(){
- // TODO : add code
- }
-
- /**
- * Returns the name of the cohort.
- * @return the name of the cohort
- */
- @Override
- public String toString() {
- // TODO : change code
- return null;
- }
-}
diff --git a/src/main/java/Grade.java b/src/main/java/Grade.java
deleted file mode 100644
index 12154f6b79462c66071149335b102342b1b409e0..0000000000000000000000000000000000000000
--- a/src/main/java/Grade.java
+++ /dev/null
@@ -1,83 +0,0 @@
-import java.util.List;
-
-/**
- * A grade with a float value comprised between 0 and 20.
- *
- */
-public class Grade {
- /**
- * The maximum value of a grade.
- */
- private static final int MAXIMUM_GRADE = 20;
- private final double value;
-
- /**
- * Constructs a grade with a value equals to the specified {@code value}.
- *
- * @param value the value of the constructed grade
- */
-
- public Grade(double value) {
- this.value = value;
- }
-
- /**
- * Returns the value of the grade as a double.
- *
- * @return the value of the grade
- */
-
- public double getValue() {
- // TODO : change code
- return 0.;
- }
-
- /**
- * Returns a string representation of the grade in the format X.X/20.
- * @return a string representation of the grade
- */
- @Override
- public String toString() {
- // TODO : change code
- return null;
- }
-
- /**
- * Returns a grade with a value equals to the arithmetic mean of the values of the grade in
- * the specified list.
- *
- * @param grades a list of grades
- * @return a grade corresponding to the mean of grade in {@code grades}
- */
- public static Grade averageGrade(List<Grade> grades){
- // TODO : change code
- return null;
- }
-
- /**
- * Determines whether or not two grades are equal. Two instances of Grade are equal if the values
- * of their {@code value} member field are the same.
- * @param o an object to be compared with this Grade
- * @return {@code true} if the object to be compared is an instance of Grade and has the same value; {@code false}
- * otherwise.
- */
- @Override
- public boolean equals(Object o) {
- if (this == o) return true;
- if (o == null || getClass() != o.getClass()) return false;
-
- Grade grade = (Grade) o;
-
- return Double.compare(grade.value, value) == 0;
- }
-
- /**
- * Returns a hash code value for the object.
- * @return a hash code value for this object.
- */
- @Override
- public int hashCode() {
- long temp = Double.doubleToLongBits(value);
- return (int) (temp ^ (temp >>> 32));
- }
-}
diff --git a/src/main/java/Main.java b/src/main/java/Main.java
deleted file mode 100644
index 43057dd29d12bbbe5656d926c6def3add7985b4b..0000000000000000000000000000000000000000
--- a/src/main/java/Main.java
+++ /dev/null
@@ -1,5 +0,0 @@
-public class Main {
- public static void main(String[] args){
- // TODO: add code.
- }
-}
diff --git a/src/main/java/Student.java b/src/main/java/Student.java
deleted file mode 100644
index 3f018e120474735e1ac185c427691ae5dd11f9ba..0000000000000000000000000000000000000000
--- a/src/main/java/Student.java
+++ /dev/null
@@ -1,108 +0,0 @@
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * A students with results.
- */
-
-public class Student {
- private final String firstName;
- private final String lastName;
- private final List<TeachingUnitResult> results;
-
- /**
- * Constructs a student with the specified first name and last name and no associated results.
- *
- * @param firstName the first name of the constructed student
- * @param lastName the last name of the constructed student
- */
-
- public Student(String firstName, String lastName) {
- this.firstName = firstName;
- this.lastName = lastName;
- this.results = new ArrayList<>();
- }
-
- /**
- * Add a grade associated to a teaching unit to the results of the student.
- *
- * @param teachingUnitName the name of the teaching unit of the added result
- * @param grade the grade of the added result
- */
- public void addResult(String teachingUnitName, Grade grade){
- // TODO : add code
- }
-
- /**
- * Returns a string representation of the student in the format first name last name.
- * @return a string representation of the student
- */
- @Override
- public String toString() {
- // TODO : change code
- return null;
- }
-
-
- /**
- * Returns the grades of the student.
- *
- * @return the grades of the student
- */
- public List<Grade> getGrades(){
- // TODO : change code
- return null;
- }
-
- /**
- * Returns the average grade of the student.
- *
- * @return the average grade of the student
- */
- public Grade averageGrade() {
- // TODO : change code
- return null;
- }
-
- /**
- * Print via the standard output the name of the student, all results associated to the students and
- * the average grade of the student.
- */
- public void printResults(){
- // TODO : add code
- }
-
-
- /**
- * Determines whether or not two students are equal. Two instances of Student are equal if the values
- * of their {@code firtName} and {@code lastName} member fields are the same.
- * @param o an object to be compared with this Student
- * @return {@code true} if the object to be compared is an instance of Student and has the same name; {@code false}
- * otherwise.
- */
- @Override
- public boolean equals(Object o) {
- if (this == o) return true;
- if (o == null || getClass() != o.getClass()) return false;
-
- Student student = (Student) o;
-
- if (!firstName.equals(student.firstName)) return false;
- return lastName.equals(student.lastName);
- }
-
- /**
- * Returns a hash code value for the object.
- * @return a hash code value for this object.
- */
- @Override
- public int hashCode() {
- int result = firstName.hashCode();
- result = 31 * result + lastName.hashCode();
- return result;
- }
-
-
-
-
-}
diff --git a/src/main/java/TeachingUnitResult.java b/src/main/java/TeachingUnitResult.java
deleted file mode 100644
index 38ddcb51104cc52b8674099ede96e00f0ee9218d..0000000000000000000000000000000000000000
--- a/src/main/java/TeachingUnitResult.java
+++ /dev/null
@@ -1,72 +0,0 @@
-/**
- * A result corresponding to a grade associated with a teaching unit.
- */
-
-public class TeachingUnitResult {
- private final String teachingUnitName;
- private final Grade grade;
-
-
- /**
- * Constructs an instance of TeachingUnitResult with a grade equals to the specified {@code grade}
- * and a teaching unit name equals to the specified {@code teachingUnitName}.
- *
- * @param teachingUnitName the name of the teaching unit of the constructed TeachingUnitResult
- * @param grade the grade of the constructed TeachingUnitResult
- */
-
- public TeachingUnitResult(String teachingUnitName, Grade grade) {
- this.teachingUnitName = teachingUnitName;
- this.grade = grade;
- }
-
- /**
- * Returns the grade associated to the result.
- *
- * @return the grade associated to the result
- */
- public Grade getGrade() {
- // TODO : change code
- return null;
- }
-
- /**
- * Returns a string representation of the result in the format Name of the teaching unit : X.X.
- * @return a string representation of the result
- */
- @Override
- public String toString() {
- // TODO : change code
- return null;
- }
-
-
- /**
- * Determines whether or not two results are equal. Two instances of TeachingUnitResult are equal if the values
- * of their {@code teachingUnitName} and {@code grade} member fields are the same.
- * @param o an object to be compared with this TeachingUnitResult
- * @return {@code true} if the object to be compared is an instance of TeachingUnitResult and has the same grad and
- * teaching unit name; {@code false} otherwise.
- */
- @Override
- public boolean equals(Object o) {
- if (this == o) return true;
- if (o == null || getClass() != o.getClass()) return false;
-
- TeachingUnitResult that = (TeachingUnitResult) o;
-
- if (!teachingUnitName.equals(that.teachingUnitName)) return false;
- return grade.equals(that.grade);
- }
-
- /**
- * Returns a hash code value for the object.
- * @return a hash code value for this object.
- */
- @Override
- public int hashCode() {
- int result = teachingUnitName.hashCode();
- result = 31 * result + grade.hashCode();
- return result;
- }
-}
diff --git a/src/main/java/mandelbrot/Complex.java b/src/main/java/mandelbrot/Complex.java
new file mode 100644
index 0000000000000000000000000000000000000000..5dd4e6b04d86e298aa9e8258c7b40e8e63eddde9
--- /dev/null
+++ b/src/main/java/mandelbrot/Complex.java
@@ -0,0 +1,246 @@
+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
+ * are created.
+ * It includes methods for addition, subtraction, multiplication, division,
+ * conjugation, and other common functions on complex numbers.
+ *
+ * @author Arnaud Labourel
+ * @author Guyslain Naves
+ */
+public class Complex {
+
+ /**
+ * The real part of a complex number.
+ */
+ private final double real;
+
+ /**
+ * The imaginary part of a complex number.
+ */
+ private final double imaginary;
+
+
+ /**
+ * Initializes a complex number with the specified real and imaginary parts.
+ *
+ * @param real the real part
+ * @param imaginary the imaginary part
+ */
+ public Complex(double real, double imaginary) {
+ this.real = real;
+ this.imaginary = imaginary;
+ }
+
+ /**
+ * Zero as a complex number, i.e., a number representing "0.0 + 0.0i".
+ */
+ public static final Complex ZERO = new Complex(0, 0);
+
+ /**
+ * One as a complex number, i.e., a number representing "1.0 + 0.0i".
+ */
+ public static final Complex ONE = new Complex(1, 0);
+
+
+ /**
+ * The square root of -1, i.e., a number representing "0.0 + 1.0i".
+ */
+ public static final 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;
+ }
+
+ /**
+ * Returns the imaginary part of this complex number.
+ *
+ * @return the imaginary part of this complex number
+ */
+ public double getImaginary() {
+ return imaginary;
+ }
+
+ /**
+ * Returns a complex number, whose multiplication corresponds to a rotation by the given angle in the complex plane.
+ * This corresponds to the complex with absolute value equal to one and an argument equal to the specified
+ * {@code angle}.
+ *
+ * @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));
+ }
+
+ /**
+ * Creates a complex number with the specified real part and an imaginary part equal to zero.
+ *
+ * @param real the real component
+ * @return the complex {@code real + 0i}
+ */
+ public static Complex real(double real) {
+ return new Complex(real, 0);
+ }
+
+ /**
+ * Returns a {@code Complex} whose value is {@code (this + addend)}.
+ *
+ * @param addend a 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);
+ }
+
+ /**
+ * Returns the negation of this complex number.
+ *
+ * @return A complex <code>c</code> such that <code>this + c = 0</code>
+ */
+ public Complex negate() {
+ return new Complex(-this.real, -this.imaginary);
+ }
+
+ /**
+ * Returns the conjugate of this complex number.
+ *
+ * @return A complex <code>c</code> such that <code>this * c = ||this|| ** 2</code>
+ */
+ public Complex conjugate() {
+ return new Complex(this.real, -this.imaginary);
+ }
+
+ /**
+ * Returns a {@code Complex} whose value is {@code (this - subtrahend)}.
+ *
+ * @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);
+ }
+
+ /**
+ * Returns a {@code Complex} whose value is {@code this * factor}
+ *
+ * @param factor the complex number to multiply to {@code this}
+ * @return the complex number {@code this * factor}
+ */
+ public Complex multiply(Complex factor) {
+ return new Complex(
+ this.real * factor.real - this.imaginary * factor.imaginary,
+ this.real * factor.imaginary + this.imaginary * factor.real);
+ }
+
+ /**
+ * Returns the squared modulus of this complex number.
+ *
+ * @return <code>||this|| ** 2</code>
+ */
+ public double squaredModulus() {
+ return real * real + imaginary * imaginary;
+ }
+
+ /**
+ * Returns the modulus (distance to zero) of this complex number.
+ *
+ * @return <code>||this||</code>
+ */
+ public double modulus() {
+ return Math.sqrt(squaredModulus());
+ }
+
+
+ /**
+ * Returns the reciprocal of this complex number.
+ *
+ * @return a complex number <code>c</code> such that <code>this * c = 1</code>
+ */
+ public Complex reciprocal() {
+ if (this.equals(ZERO)){
+ throw new ArithmeticException("divide by zero");
+ }
+ double m = squaredModulus();
+ return this.conjugate().scale(1. / m);
+ }
+
+ /**
+ * Returns a {@code Complex} whose value is <code>this / divisor</code>.
+ *
+ * @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());
+ }
+
+
+ /**
+ * Returns the integral power of this complex number.
+ *
+ * @param p a non-negative integer
+ * @return the complex number <code>this ** p</code>
+ */
+ public Complex pow(int p) {
+ if (p == 0)
+ return ONE;
+ Complex result = (this.multiply(this)).pow(p / 2);
+ if (p % 2 == 1)
+ result = result.multiply(this);
+ return result;
+ }
+
+ /**
+ * Returns the scalar multiplication of this complex number.
+ *
+ * @param lambda a scalar number
+ * @return the complex number <code>lambda * this</code>
+ */
+ public Complex scale(double lambda) {
+ return new Complex(lambda * real, lambda * imaginary);
+ }
+
+ /**
+ * Test for equality with another object. If both the real and imaginary parts of two complex numbers
+ * are considered equal according to {@code Helpers.doubleCompare} (i.e., within {@code Helpers.RANGE}), the two
+ * Complex objects are considered to be equal.
+ *
+ * @param other Object to test for equality with this instance.
+ * @return {@code true} if the objects are equal, {@code false} if object is {@code null}, not an instance of
+ * {@code Complex}, or not equal to this instance.
+ */
+ @Override
+ public boolean equals(Object other) {
+ if (this == other)
+ return true;
+ if (!(other instanceof Complex))
+ return false;
+ Complex complex = (Complex) other;
+ return Helpers.doubleCompare(complex.real, real) == 0 &&
+ Helpers.doubleCompare(complex.imaginary, imaginary) == 0;
+ }
+
+ /**
+ * Returns a string representation of this complex number.
+ *
+ * @return a string representation of this complex number of the form 42.0 - 1024.0i.
+ */
+ @Override
+ public String toString() {
+ if (Helpers.doubleCompare(imaginary, 0) == 0) return real + "";
+ if (Helpers.doubleCompare(real, 0) == 0) return imaginary + "i";
+ if (Helpers.doubleCompare(imaginary, 0) < 0) return real + " - " + (-imaginary) + "i";
+ return real + " + " + imaginary + "i";
+ }
+}
diff --git a/src/main/java/mandelbrot/Helpers.java b/src/main/java/mandelbrot/Helpers.java
new file mode 100644
index 0000000000000000000000000000000000000000..39708dc402de32073f9823293b07f685ba1ced3a
--- /dev/null
+++ b/src/main/java/mandelbrot/Helpers.java
@@ -0,0 +1,29 @@
+package mandelbrot;
+
+/**
+ * Some helpful functions and values.
+ */
+class Helpers {
+ /**
+ * A small double used to bound the precision of the comparison of doubles.
+ */
+ final static double EPSILON = 1e-9;
+
+ /**
+ * Comparison of doubles (up to <code>EPSILON</code>)
+ * <p>
+ * Please note that floating-point comparison is very tricky, this function
+ * is not suited to compare small floating-point numbers.
+ *
+ * @param d1 an arbitrary double
+ * @param d2 an arbitrary double
+ * @return the result of comparing <code>d1</code> and <code>d2</code>.
+ */
+ static int doubleCompare(double d1, double d2) {
+ double diff = d1 - d2;
+ return
+ (diff > EPSILON) ? 1 :
+ (diff < -EPSILON) ? -1 :
+ 0;
+ }
+}
diff --git a/src/main/java/mandelbrot/Mandelbrot.java b/src/main/java/mandelbrot/Mandelbrot.java
new file mode 100644
index 0000000000000000000000000000000000000000..0721c43d78897885f4044e20e2533744e1c8c8a8
--- /dev/null
+++ b/src/main/java/mandelbrot/Mandelbrot.java
@@ -0,0 +1,115 @@
+package mandelbrot;
+
+import java.util.function.Function;
+
+/**
+ * A class to compute how fast a paramaterized polynomial sequence diverges.
+ * This is used to compute the colors of point in the Mandelbrot fractal.
+ */
+public class Mandelbrot {
+
+ /**
+ * If a complex has modulus above <code>RADIUS</code>, we know that
+ * the sequence diverges. <code>RADIUS</code> should be at least 2 for
+ * the usual Mandelbrot sequence.
+ */
+ private static double RADIUS = 10;
+
+ /**
+ * The square of <code>RADIUS</code>, used in computations.
+ */
+ private static 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
+ * quality, specially in details of the fractal, but also the slower
+ * the computation is.
+ */
+ private static int MAX_ITERATIONS = 1000;
+
+
+ /**
+ * The degree of the polynomial defining the sequence.
+ */
+ private static 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>.
+ */
+ public double divergence(Complex c) {
+ if (isConvergent(c)) return Double.POSITIVE_INFINITY;
+ Function<Complex, Complex> f = z -> z.pow(DEGREE).add(c);
+ Sequence seq = new Sequence(c, f);
+ int countIterations = 0;
+ for (Complex z : seq) {
+ if (isDiverging(z))
+ return smoothIterationCount(countIterations, z);
+ if (countIterations >= MAX_ITERATIONS)
+ return Double.POSITIVE_INFINITY;
+ countIterations++;
+ }
+ return 0.;
+ }
+
+ /**
+ * This method is used to smooth the number of iterations until
+ * getting out of the <code>RADIUS</code>, so that we get a
+ * floating-point value and thus smooth coloring.
+ *
+ * @param countIterations the iteration on which <code>RADIUS</code> is beaten.
+ * @param z the first complex of the sequence whose modulus is above <code>RADIUS</code>
+ * @return a double close to <code>countIterations</code>.
+ */
+ private double smoothIterationCount(int countIterations, Complex z) {
+ double x = Math.log(z.modulus()) / Math.log(RADIUS);
+ return (double) countIterations - Math.log(x) / Math.log(DEGREE);
+
+ }
+
+
+ /**
+ * Checks whether a term of the sequence is out of the given
+ * <code>RADIUS</code>, which guarantees that the sequence diverges.
+ *
+ * @param z a term of the sequence
+ * @return <code>true</code> if we are sure that the sequence diverges.
+ */
+ private boolean isDiverging(Complex z) {
+ return z.squaredModulus() > RADIUS2;
+ }
+
+
+ /**
+ * Checks whether the parameter of the sequence is in some region
+ * that guarantees that the sequence is convergent. This does not
+ * capture all convergent parameters.
+ *
+ * @param c the parameter for the polynomial
+ * @return <code>true</code> if we are sure that the sequence converges.
+ */
+ private boolean isConvergent(Complex c) {
+ return isIn2Bulb(c) || isInCardioid(c);
+ }
+
+ /* The cardioid black shape of the fractal */
+ private boolean isInCardioid(Complex z) {
+ double m = z.squaredModulus();
+ return Helpers.doubleCompare(m * (8 * m - 3), 3. / 32. - z.getReal()) <= 0;
+ }
+
+ /* The main black disc of the fractal */
+ private boolean isIn2Bulb(Complex z) {
+ Complex zMinusOne = z.subtract(new Complex(-1, 0));
+ return Helpers.doubleCompare(zMinusOne.squaredModulus(), 1. / 16.) < 0;
+ }
+
+
+}
diff --git a/src/main/java/mandelbrot/Sequence.java b/src/main/java/mandelbrot/Sequence.java
new file mode 100644
index 0000000000000000000000000000000000000000..8d84cf1f1e82d90e3cc9bb9c91523ae7759ae2fc
--- /dev/null
+++ b/src/main/java/mandelbrot/Sequence.java
@@ -0,0 +1,59 @@
+package mandelbrot;
+
+import java.util.Iterator;
+import java.util.function.Function;
+
+/**
+ * A class to compute the term of a sequence of complex numbers, generated
+ * by a function <code>f</code> and an initial term <code>u_0</code>, such
+ * that <code> u_{n+1} = f(u_n)</code>.
+ * <p>
+ * 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;
+
+
+ /**
+ * Creates a sequence given the initial term and the function.
+ *
+ * @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;
+ }
+
+
+ /**
+ * Creates an iterator iterating all terms of the sequence in order.
+ *
+ * @return an iterator
+ */
+ @Override
+ public Iterator<Complex> iterator() {
+ return new SeqIterator();
+ }
+
+ private class SeqIterator implements Iterator<Complex> {
+ private Complex current = u0;
+
+ @Override
+ public boolean hasNext() {
+ return true;
+ }
+
+ @Override
+ public Complex next() {
+ current = f.apply(current);
+ return current;
+ }
+ }
+}
diff --git a/src/main/java/viewer/Camera.java b/src/main/java/viewer/Camera.java
new file mode 100644
index 0000000000000000000000000000000000000000..7edad181d76e01e557c127d0c0fe9f934935f5d1
--- /dev/null
+++ b/src/main/java/viewer/Camera.java
@@ -0,0 +1,55 @@
+package viewer;
+
+import mandelbrot.Complex;
+
+/**
+ * A class to represent the view (a rectangle over the complex plane)
+ * to be displayed. Some interesting views are already defined.
+ */
+class Camera {
+
+ /**
+ * The high-level view of the Mandelbrot set.
+ */
+ static Camera camera0 =
+ new Camera(
+ -0.5,
+ 0.,
+ 3,
+ 4. / 3.);
+
+
+
+
+ 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 */
+
+
+ /**
+ * 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 width the width of the rectangle to display
+ * @param aspectRatio the ratio width/height of the rectangle to display
+ */
+ private Camera(double centerX, double centerY, double width, double aspectRatio) {
+ this.width = Complex.real(width);
+ this.height = new Complex(0, width / aspectRatio);
+ this.center = new Complex(centerX, centerY);
+ }
+
+ /**
+ * Converts position relative to the rectangle defining the view
+ * into absolute complex numbers.
+ *
+ * @param tx horizontal relative position, between 0 (left) and 1 (right)
+ * @param ty vertical relative position, between 0 (bottom) and 1 (top)
+ * @return the complex at this position of the rectangle
+ */
+ Complex toComplex(double tx, double ty) {
+ return center.add(width.scale(tx - 0.5)).add(height.scale(ty - 0.5));
+ }
+
+}
diff --git a/src/main/java/viewer/Controller.java b/src/main/java/viewer/Controller.java
new file mode 100644
index 0000000000000000000000000000000000000000..df7db671bb5017fa25af48494a2ca5ac5610377c
--- /dev/null
+++ b/src/main/java/viewer/Controller.java
@@ -0,0 +1,160 @@
+package viewer;
+
+import javafx.fxml.FXML;
+import javafx.fxml.Initializable;
+import javafx.scene.canvas.Canvas;
+import javafx.scene.canvas.GraphicsContext;
+import javafx.scene.paint.Color;
+import mandelbrot.Complex;
+import mandelbrot.Mandelbrot;
+
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.ResourceBundle;
+
+/**
+ * Controls the color of the pixels of the canvas.
+ */
+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>
+ */
+ private static final int SUPERSAMPLING = 3;
+
+ @FXML
+ private Canvas canvas; /* The canvas to draw on */
+
+ private Camera camera = Camera.camera0; /* The view to display */
+
+ private 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};
+ /* colors of the histogram */
+ private Color[] colors =
+ {Color.gray(0.2),
+ Color.gray(0.7),
+ Color.rgb(55, 118, 145),
+ Color.rgb(63, 74, 132),
+ Color.rgb(145, 121, 82),
+ Color.rgb(250, 250, 200)
+ };
+ /* algorithm to generate the distribution of colors */
+ private Histogram histogram = new Histogram(breakpoints, colors);
+
+ /**
+ * Method called when the graphical interface is loaded
+ *
+ * @param location location
+ * @param resources resources
+ */
+ @Override
+ public void initialize(URL location, ResourceBundle resources) {
+ render();
+ }
+
+ /**
+ * compute and display the image.
+ */
+ private void render() {
+ List<Pixel> pixels = getPixels();
+ renderPixels(pixels);
+ }
+
+ /**
+ * display each pixel
+ *
+ * @param pixels the list of all the pixels to display
+ */
+ private void renderPixels(List<Pixel> pixels) {
+ GraphicsContext context = canvas.getGraphicsContext2D();
+ for (Pixel pix : pixels) {
+ pix.render(context);
+ }
+ }
+
+ /**
+ * Attributes to each subpixel a color
+ *
+ * @param subPixels the list of all subpixels to display
+ */
+ private void setSubPixelsColors(List<SubPixel> subPixels) {
+ int nonBlackPixelsCount = countNonBlackSubPixels(subPixels);
+ if (nonBlackPixelsCount == 0) return;
+ Color[] colors = histogram.generate(nonBlackPixelsCount);
+ subPixels.sort(SubPixel::compare);
+ int pixCount = 0;
+ for (SubPixel pix : subPixels) {
+ pix.setColor(colors[pixCount]);
+ pixCount++;
+ if (pixCount >= colors.length) // remaining subpixels stay black (converge).
+ break;
+ }
+ }
+
+
+ /**
+ * Count how many subpixel diverge.
+ *
+ * @param subPixels the subpixels to display
+ * @return the number of diverging subpixels
+ */
+ private int countNonBlackSubPixels(List<SubPixel> subPixels) {
+ return (int)
+ subPixels.stream()
+ .filter(pix -> pix.value != Double.POSITIVE_INFINITY)
+ .count();
+ }
+
+ /**
+ * Generates the list of all the pixels in the canvas
+ *
+ * @return the list of pixels
+ */
+ private List<Pixel> getPixels() {
+ int width = (int) canvas.getWidth();
+ int height = (int) canvas.getHeight();
+ List<SubPixel> subPixels =
+ new ArrayList<>(width * height * SUPERSAMPLING * SUPERSAMPLING);
+ List<Pixel> pixels =
+ new ArrayList<>(width * height);
+ for (int x = 0; x < width; x++) {
+ for (int y = 0; y < height; y++) {
+ Pixel pix = preparePixel(x, y);
+ subPixels.addAll(pix.getSubPixels());
+ pixels.add(pix);
+ }
+ }
+ setSubPixelsColors(subPixels);
+ return pixels;
+ }
+
+ /**
+ * Create the pixel with given coordinates
+ *
+ * @param x horizontal coordinate of the pixel
+ * @param y vertical coordinate of the pixel
+ * @return the computed pixel with given coordinates
+ */
+ private Pixel preparePixel(int x, int y) {
+ double width = SUPERSAMPLING * canvas.getWidth();
+ double height = SUPERSAMPLING * canvas.getHeight();
+ List<SubPixel> sampledSubPixels = new ArrayList<>();
+ for (int i = 0; i < SUPERSAMPLING; i++) {
+ for (int j = 0; j < SUPERSAMPLING; j++) {
+ Complex z =
+ camera.toComplex(
+ ((double) (SUPERSAMPLING * x) + i) / width,
+ 1 - ((double) (SUPERSAMPLING * y) + j) / height // invert y-axis
+ );
+ double divergence = mandelbrot.divergence(z);
+ sampledSubPixels.add(new SubPixel(divergence));
+ }
+ }
+ return new Pixel(x, y, sampledSubPixels);
+ }
+}
diff --git a/src/main/java/viewer/Histogram.java b/src/main/java/viewer/Histogram.java
new file mode 100644
index 0000000000000000000000000000000000000000..2292ac62f7b294613c494bae03ba5b7838397dfc
--- /dev/null
+++ b/src/main/java/viewer/Histogram.java
@@ -0,0 +1,56 @@
+package viewer;
+
+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.
+ */
+class Histogram {
+
+ private double[] breakpoints;
+ private Color[] colors;
+
+ /**
+ * Creates a schema of colors.
+ * <code>breakpoints</code> and <code>colors</code> must have the same length.
+ * Two consecutive indices of <code>colors</code> define a gradient of colors.
+ * Those colors will be linearly mapped to the interval defined by the same
+ * indices taken in <code>breakpoints</code>
+ * For instance, { 0, 0.4, 1.} with { BLACK, RED, WHITE} represents a black
+ * to red to white spectrum, where 40% of the point are the black to red
+ * gradient, 60% are the red to white gradient.
+ *
+ * @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) {
+ assert (breakpoints[0] == 0);
+ assert (breakpoints[breakpoints.length - 1] == 1);
+ assert (colors.length == breakpoints.length);
+ this.breakpoints = breakpoints;
+ this.colors = colors;
+ }
+
+
+ /**
+ * Generates a list of colors of given length representing this spectrum.
+ *
+ * @param howManyPoints the number of colors returned
+ * @return a list of colors following the schema defined in the constructor
+ */
+ 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;
+ while (absolute > breakpoints[bpIndex + 1] && bpIndex < breakpoints.length - 1)
+ bpIndex++;
+ double relative = (absolute - breakpoints[bpIndex]) / (breakpoints[bpIndex + 1] - breakpoints[bpIndex]);
+ result[ptIndex] = colors[bpIndex].interpolate(colors[bpIndex + 1], relative);
+ }
+ return result;
+ }
+
+}
diff --git a/src/main/java/viewer/Main.java b/src/main/java/viewer/Main.java
new file mode 100644
index 0000000000000000000000000000000000000000..f1d621c406af2fc6f009990736f78ae17544a061
--- /dev/null
+++ b/src/main/java/viewer/Main.java
@@ -0,0 +1,23 @@
+package viewer;
+
+import javafx.application.Application;
+import javafx.fxml.FXMLLoader;
+import javafx.scene.Parent;
+import javafx.scene.Scene;
+import javafx.stage.Stage;
+
+public class Main extends Application {
+
+ @Override
+ public void start(Stage primaryStage) throws Exception {
+ Parent root = FXMLLoader.load(getClass().getClassLoader().getResource("viewer/viewer.fxml"));
+ primaryStage.setTitle("Mandelbrot");
+ primaryStage.setScene(new Scene(root, 1200, 900));
+ primaryStage.show();
+ }
+
+
+ public static void main(String[] args) {
+ launch(args);
+ }
+}
diff --git a/src/main/java/viewer/Pixel.java b/src/main/java/viewer/Pixel.java
new file mode 100644
index 0000000000000000000000000000000000000000..7c77c2894f1ae1543ff4184c636ddfac37e3cacc
--- /dev/null
+++ b/src/main/java/viewer/Pixel.java
@@ -0,0 +1,69 @@
+package viewer;
+
+import javafx.scene.canvas.GraphicsContext;
+import javafx.scene.paint.Color;
+
+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.
+ */
+class Pixel {
+
+ private final int x;
+ private final int y;
+ private final Collection<SubPixel> subPixels;
+
+ /**
+ * Creates a pixel with given coordinates and subpixels.
+ *
+ * @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
+ */
+ Pixel(int x, int y, Collection<SubPixel> subPixels) {
+ this.x = x;
+ this.y = y;
+ this.subPixels = subPixels;
+ }
+
+
+ /**
+ * @return the list of subpixels in this pixel
+ */
+ Collection<SubPixel> getSubPixels() {
+ return subPixels;
+ }
+
+
+ private Color getAverageColor() {
+ double red = 0;
+ double green = 0;
+ double blue = 0;
+ int count = 0;
+ for (SubPixel subPixel : subPixels) {
+ count++;
+ Color col = subPixel.getColor();
+ red += col.getRed();
+ green += col.getGreen();
+ blue += col.getBlue();
+ }
+ double c = (double) count;
+ return new Color(red / c, green / c, blue / c, 1.);
+ }
+
+
+ /**
+ * Displays the pixel.
+ *
+ * @param context the context of the canvas on which to paint.
+ */
+ void render(GraphicsContext context) {
+ context.setFill(getAverageColor());
+ context.fillRect((double) x, (double) y, 1, 1);
+ }
+
+
+}
diff --git a/src/main/java/viewer/SubPixel.java b/src/main/java/viewer/SubPixel.java
new file mode 100644
index 0000000000000000000000000000000000000000..570b70f48743cf627c85a5f37657fbaf2e11df43
--- /dev/null
+++ b/src/main/java/viewer/SubPixel.java
@@ -0,0 +1,56 @@
+package viewer;
+
+
+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.
+ */
+
+class SubPixel {
+
+ private Color color = Color.BLACK;
+
+ /**
+ * Each subpixel has a value that will be used to color them.
+ */
+ final double value;
+
+
+ /**
+ * Creates a subpixel.
+ *
+ * @param value divergence for the corresponding pixel. This will be mapped to a color.
+ */
+ SubPixel(double value) {
+ this.value = value;
+ }
+
+ /**
+ * Attributes a color to a subpixel.
+ *
+ * @param color the color to give to the subpixel
+ */
+ void setColor(Color color) {
+ this.color = color;
+ }
+
+ /**
+ * @return the color of the subpixel. Default is black.
+ */
+ Color getColor() {
+ return color;
+ }
+
+ /**
+ * Comparison of two subpixels by their values.
+ *
+ * @param pix1 first subpixel to compare
+ * @param pix2 second subpixel to compare
+ * @return an integer representing the result of the comparison, with the usual convention.
+ */
+ static int compare(SubPixel pix1, SubPixel pix2) {
+ return Double.compare(pix1.value, pix2.value);
+ }
+}
diff --git a/src/main/resources/viewer/viewer.fxml b/src/main/resources/viewer/viewer.fxml
new file mode 100644
index 0000000000000000000000000000000000000000..93e243f8c4aa72de69ed3034b666ce3777bef6ba
--- /dev/null
+++ b/src/main/resources/viewer/viewer.fxml
@@ -0,0 +1,8 @@
+<?import javafx.scene.layout.GridPane?>
+
+<?import javafx.scene.canvas.Canvas?>
+<GridPane fx:controller="viewer.Controller"
+ xmlns:fx="http://javafx.com/fxml" alignment="center" hgap="10" vgap="10">
+
+ <Canvas fx:id="canvas" width="1200" height="900"/>
+</GridPane>
\ No newline at end of file
diff --git a/src/test/java/StandardOutputSandbox.java b/src/test/java/StandardOutputSandbox.java
deleted file mode 100644
index b8bc1dbae6823585699aaba671439a3cce1462b6..0000000000000000000000000000000000000000
--- a/src/test/java/StandardOutputSandbox.java
+++ /dev/null
@@ -1,27 +0,0 @@
-import java.io.ByteArrayOutputStream;
-import java.io.OutputStream;
-import java.io.PrintStream;
-
-
-public class StandardOutputSandbox implements Runnable {
- static String NEW_LINE = System.getProperty("line.separator");
- private Runnable runnable;
- private OutputStream outputStream;
-
- StandardOutputSandbox(Runnable runnable) {
- this.runnable = runnable;
- }
-
- public void run(){
- outputStream = new ByteArrayOutputStream();
- PrintStream printStream = new PrintStream(outputStream);
- System.setOut(printStream);
- runnable.run();
- PrintStream originalOut = System.out;
- System.setOut(originalOut);
- }
-
- String getProducedOutput() {
- return outputStream.toString();
- }
-}
diff --git a/src/test/java/TestCohort.java b/src/test/java/TestCohort.java
deleted file mode 100644
index 0ff9b4f9974b90e7bfab2839f8bfb805ecc5e0db..0000000000000000000000000000000000000000
--- a/src/test/java/TestCohort.java
+++ /dev/null
@@ -1,43 +0,0 @@
-import org.junit.jupiter.api.BeforeAll;
-import org.junit.jupiter.api.Test;
-
-import java.util.List;
-
-import static org.junit.jupiter.api.Assertions.assertEquals;
-
-class TestCohort {
- private static Cohort cohort = new Cohort("L2 informatique");
-
- @BeforeAll
- static void addStudentsToCohort(){
- Student paulCalcul = new Student("Paul", "Calcul");
- Student pierreKiroul = new Student("Pierre", "Kiroul");
- pierreKiroul.addResult("Programmation 2", TestGrade.ten);
- pierreKiroul.addResult("Structures discrètes", TestGrade.zero);
- paulCalcul.addResult("Programmation 2", TestGrade.ten);
- paulCalcul.addResult("Structures discrètes", TestGrade.twenty);
- cohort.addStudent(paulCalcul);
- cohort.addStudent(pierreKiroul);
- }
-
- @Test
- void testGetStudents(){
- assertEquals(List.of(TestStudent.paulCalcul, TestStudent.pierreKiroul), cohort.getStudents());
- }
-
- @Test
- void testPrintStudentsResults() {
- StandardOutputSandbox standardOutputSandbox = new StandardOutputSandbox(() ->cohort.printStudentsResults());
- String expectedOutput = "L2 informatique" + StandardOutputSandbox.NEW_LINE + StandardOutputSandbox.NEW_LINE
- + "Paul Calcul" + StandardOutputSandbox.NEW_LINE
- + "Programmation 2 : 10.0/20" + StandardOutputSandbox.NEW_LINE
- + "Structures discrètes : 20.0/20" + StandardOutputSandbox.NEW_LINE
- + "Note moyenne : 15.0/20" + StandardOutputSandbox.NEW_LINE + StandardOutputSandbox.NEW_LINE
- + "Pierre Kiroul" + StandardOutputSandbox.NEW_LINE
- + "Programmation 2 : 10.0/20" + StandardOutputSandbox.NEW_LINE
- + "Structures discrètes : 0.0/20" + StandardOutputSandbox.NEW_LINE
- + "Note moyenne : 5.0/20" + StandardOutputSandbox.NEW_LINE + StandardOutputSandbox.NEW_LINE;
- standardOutputSandbox.run();
- assertEquals(expectedOutput, standardOutputSandbox.getProducedOutput());
- }
-}
diff --git a/src/test/java/TestGrade.java b/src/test/java/TestGrade.java
deleted file mode 100644
index 8c25076faab40a0adc21be2687a746d58765c884..0000000000000000000000000000000000000000
--- a/src/test/java/TestGrade.java
+++ /dev/null
@@ -1,32 +0,0 @@
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertNotEquals;
-
-import org.junit.jupiter.api.Test;
-
-import java.util.List;
-
-class TestGrade {
- static Grade twenty = new Grade(20);
- static Grade zero = new Grade(0);
- static Grade ten = new Grade(10);
- private static List<Grade> grades = List.of(zero, twenty, ten);
- private static List<Grade> gradesZero = List.of(zero, zero);
-
- @Test
- void testGetValue() {
- assertEquals(20, twenty.getValue());
- assertEquals(0, zero.getValue());
- }
-
- @Test
- void testToString() {
- assertEquals("20.0/20", twenty.toString());
- assertEquals("0.0/20", zero.toString());
- }
-
- @Test
- void testAverageGrade(){
- assertEquals(ten, Grade.averageGrade(grades));
- assertEquals(zero, Grade.averageGrade(gradesZero));
- }
-}
diff --git a/src/test/java/TestStudent.java b/src/test/java/TestStudent.java
deleted file mode 100644
index 97ece12db7d2047da0e81ba2033da091b450d9dd..0000000000000000000000000000000000000000
--- a/src/test/java/TestStudent.java
+++ /dev/null
@@ -1,54 +0,0 @@
-import static org.junit.jupiter.api.Assertions.assertEquals;
-
-import org.junit.jupiter.api.BeforeAll;
-import org.junit.jupiter.api.Test;
-
-import java.util.List;
-
-class TestStudent {
- private static Student arnaudLabourel = new Student("Arnaud", "Labourel");
- static Student paulCalcul = new Student("Paul", "Calcul");
- static Student pierreKiroul = new Student("Pierre", "Kiroul");
-
- @BeforeAll
- static void addResultsToStudents(){
- arnaudLabourel.addResult("Programmation 2", TestGrade.twenty);
- arnaudLabourel.addResult("Structures discrètes", TestGrade.twenty);
- pierreKiroul.addResult("Programmation 2", TestGrade.ten);
- pierreKiroul.addResult("Structures discrètes", TestGrade.zero);
- paulCalcul.addResult("Programmation 2", TestGrade.ten);
- paulCalcul.addResult("Structures discrètes", TestGrade.twenty);
- }
-
- @Test
- void testToString() {
- assertEquals("Paul Calcul", paulCalcul.toString());
- assertEquals("Pierre Kiroul", pierreKiroul.toString());
- }
-
- @Test
- void testGetGrades() {
- assertEquals(List.of(TestGrade.twenty, TestGrade.twenty), arnaudLabourel.getGrades());
- assertEquals(List.of(TestGrade.ten, TestGrade.zero), pierreKiroul.getGrades());
- assertEquals(List.of(TestGrade.ten, TestGrade.twenty), paulCalcul.getGrades());
- }
-
- @Test
- void testGetAverageGrade() {
- assertEquals(TestGrade.twenty, arnaudLabourel.averageGrade());
- assertEquals(new Grade(5), pierreKiroul.averageGrade());
- assertEquals(new Grade(15), paulCalcul.averageGrade());
- }
-
- @Test
- void testPrintResults() {
- StandardOutputSandbox standardOutputSandbox = new StandardOutputSandbox(() ->arnaudLabourel.printResults());
- String expectedOutput =
- "Arnaud Labourel" + StandardOutputSandbox.NEW_LINE
- + "Programmation 2 : 20.0/20" + StandardOutputSandbox.NEW_LINE
- + "Structures discrètes : 20.0/20" + StandardOutputSandbox.NEW_LINE
- + "Note moyenne : 20.0/20" + StandardOutputSandbox.NEW_LINE;
- standardOutputSandbox.run();
- assertEquals(expectedOutput, standardOutputSandbox.getProducedOutput());
- }
-}
\ No newline at end of file
diff --git a/src/test/java/TestTeachingUnitResult.java b/src/test/java/TestTeachingUnitResult.java
deleted file mode 100644
index 10525b19b7b9759576fc8195fc39c5b96d579c59..0000000000000000000000000000000000000000
--- a/src/test/java/TestTeachingUnitResult.java
+++ /dev/null
@@ -1,21 +0,0 @@
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import org.junit.jupiter.api.Test;
-
-class TestTeachingUnitResult {
- private static TeachingUnitResult twentyAtProg =
- new TeachingUnitResult("Programmation 2", TestGrade.twenty);
- private static TeachingUnitResult zeroAtStructDiscrete =
- new TeachingUnitResult("Structures discrètes", TestGrade.zero);
-
- @Test
- void testGetGrade() {
- assertEquals(TestGrade.twenty, twentyAtProg.getGrade());
- assertEquals(TestGrade.zero, zeroAtStructDiscrete.getGrade());
- }
-
- @Test
- void testToString() {
- assertEquals("Programmation 2 : 20.0/20", twentyAtProg.toString());
- assertEquals("Structures discrètes : 0.0/20", zeroAtStructDiscrete.toString());
- }
-}