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

First version of mandelbrot.

parent 047d15d6
No related branches found
No related tags found
No related merge requests found
Showing
with 890 additions and 402 deletions
# 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 ## Membres du projet
......
plugins { plugins {
id "application" id 'application'
id "org.openjfx.javafxplugin" version "0.0.10"
} }
apply plugin : "java"
group 'l2info' javafx {
version '1.0-SNAPSHOT' version = "17"
modules = [ 'javafx.controls', 'javafx.fxml' ]
}
sourceCompatibility = "16"
targetCompatibility = "16"
repositories { repositories {
mavenCentral() mavenCentral()
...@@ -20,7 +25,7 @@ test { ...@@ -20,7 +25,7 @@ test {
} }
ext { ext {
javaMainClass = "Main" javaMainClass = "viewer.Main"
} }
application { application {
......
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;
}
}
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));
}
}
public class Main {
public static void main(String[] args){
// TODO: add code.
}
}
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;
}
}
/**
* 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;
}
}
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";
}
}
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;
}
}
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 -&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>.
*/
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;
}
}
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;
}
}
}
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));
}
}
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);
}
}
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;
}
}
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);
}
}
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);
}
}
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);
}
}
<?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
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();
}
}
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());
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment