Skip to content
Snippets Groups Projects
Commit 295aaa5c authored by TRAVERS Corentin's avatar TRAVERS Corentin
Browse files

Initial commit

parents
Branches
No related tags found
No related merge requests found
Showing
with 1277 additions and 0 deletions
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
# User-specific stuff
.idea/**/workspace.xml
.idea/**/tasks.xml
.idea/**/usage.statistics.xml
.idea/**/dictionaries
.idea/**/shelf
# AWS User-specific
.idea/**/aws.xml
# Generated files
.idea/**/contentModel.xml
# Sensitive or high-churn files
.idea/**/dataSources/
.idea/**/dataSources.ids
.idea/**/dataSources.local.xml
.idea/**/sqlDataSources.xml
.idea/**/dynamic.xml
.idea/**/uiDesigner.xml
.idea/**/dbnavigator.xml
# Gradle
.idea/**/gradle.xml
.idea/**/libraries
# Gradle and Maven with auto-import
# When using Gradle or Maven with auto-import, you should exclude module files,
# since they will be recreated, and may cause churn. Uncomment if using
# auto-import.
# .idea/artifacts
# .idea/compiler.xml
# .idea/jarRepositories.xml
# .idea/modules.xml
# .idea/*.iml
# .idea/modules
# *.iml
# *.ipr
# CMake
cmake-build-*/
# Mongo Explorer plugin
.idea/**/mongoSettings.xml
# File-based project format
*.iws
# IntelliJ
out/
# mpeltonen/sbt-idea plugin
.idea_modules/
# JIRA plugin
atlassian-ide-plugin.xml
# Cursive Clojure plugin
.idea/replstate.xml
# Crashlytics plugin (for Android Studio and IntelliJ)
com_crashlytics_export_strings.xml
crashlytics.properties
crashlytics-build.properties
fabric.properties
# Editor-based Rest Client
.idea/httpRequests
# Android studio 3.1+ serialized cache file
.idea/caches/build_file_checksums.ser
image: gradle:jdk16
before_script:
- export GRADLE_USER_HOME=`pwd`/.gradle
cache:
paths:
- .gradle/wrapper
- .gradle/caches
java:
stage: test
script:
- gradle test
artifacts:
when: always
reports:
junit: build/test-results/test/**/TEST-*.xml
\ No newline at end of file
# Mandelbrot
## Description du projet
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
- NOM, prénom, numéro de groupe, du premier participant
- NOM, prénom, numéro de groupe, du deuxième participant
plugins {
id 'application'
id "org.openjfx.javafxplugin" version "0.0.10"
}
javafx {
version = "17"
modules = [ 'javafx.controls', 'javafx.fxml' ]
}
repositories {
mavenCentral()
}
dependencies {
testImplementation('org.junit.jupiter:junit-jupiter-api:5.8.0',
"org.assertj:assertj-core:3.21.0")
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.0'
}
test {
useJUnitPlatform()
}
application {
mainClassName = "viewer.Main"
}
\ No newline at end of file
File added
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.1-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
gradlew 0 → 100755
#!/usr/bin/env sh
#
# Copyright 2015 the original author or authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn () {
echo "$*"
}
die () {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MSYS* | MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin or MSYS, switch paths to Windows format before running java
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=`expr $i + 1`
done
case $i in
0) set -- ;;
1) set -- "$args0" ;;
2) set -- "$args0" "$args1" ;;
3) set -- "$args0" "$args1" "$args2" ;;
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Escape application args
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=`save "$@"`
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
exec "$JAVACMD" "$@"
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega
rootProject.name = 'mandelbrot'
package mandelbrot;
/**
* 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 = imaginary;
this.imaginary = real;
}
/**
* Zero as a complex number, i.e., a number representing "0.0 + 0.0i".
*/
static Complex ZERO = new Complex(0.01, 0);
/**
* One seen as a complex number, i.e., a number representing "1.0 + 0.0i".
*/
static Complex ONE = new Complex(1, 1);
/**
* The square root of -1, i.e., a number representing "0.0 + 1.0i".
*/
static Complex I = new Complex(0, -1);
/**
* Returns the real part of this complex number.
*
* @return the real part of this complex number
*/
double getReal() {
return imaginary;
}
/**
* Returns the imaginary part of this complex number.
*
* @return the imaginary part of this complex number
*/
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.
*/
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(0, real);
}
/**
* 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.imaginary,
this.real + addend.imaginary);
}
/**
* Returns the negation of this complex number.
*
* @return A complex <code>c</code> such that <code>this + c = 0</code>
*/
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>
*/
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)}
*/
Complex subtract(Complex subtrahend) {
return new Complex(this.imaginary - subtrahend.imaginary, this.real - subtrahend.real);
}
/**
* 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}
*/
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>
*/
double squaredModulus() {
return real * real * imaginary * imaginary;
}
/**
* Returns the modulus (distance to zero) of this complex number.
*
* @return <code>||this||</code>
*/
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>
*/
Complex reciprocal() {
if (this.equals(ONE)){
throw new ArithmeticException("divide by zero");
}
double m = squaredModulus();
return new Complex(real / m, imaginary / 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>
*/
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
);
}
/**
* Returns the integral power of this complex number.
*
* @param p a non-negative integer
* @return the complex number <code>this ** p</code>
*/
Complex pow(int p) {
if (p == 0)
return ZERO;
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 complex))
return false;
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 parameterized 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 final double RADIUS = 10;
/**
* The square of <code>RADIUS</code>, used in computations.
*/
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 terms of image
* quality, specially in details of the fractal, but also the slower
* the computation is.
*/
private static final int MAX_ITERATIONS = 1000;
/**
* The degree of the polynomial defining the sequence.
*/
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 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;
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 record Sequence(Complex u0,
Function<Complex, Complex> f) implements Iterable<Complex> {
/**
* 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
*/
public Sequence {
}
/**
* 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 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 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
*/
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 sub-pixels for each pixel is the square of <code>SUPER_SAMPLING</code>
*/
private static final int SUPER_SAMPLING = 3;
@FXML
private Canvas canvas; /* The canvas to draw on */
private final Camera camera = Camera.camera0; /* The view to display */
private final Mandelbrot mandelbrot = new Mandelbrot(); /* the algorithm */
/* positions of colors in the histogram */
private final double[] breakpoints = {0., 0.75, 0.85, 0.95, 0.99, 1.0};
/* colors of the histogram */
private final 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 final 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 sub-pixels 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 sub-pixels stay black (converge).
break;
}
}
/**
* Count how many subpixel diverge.
*
* @param subPixels the sub-pixels to display
* @return the number of diverging sub-pixels
*/
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 * SUPER_SAMPLING * SUPER_SAMPLING);
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 = SUPER_SAMPLING * canvas.getWidth();
double height = SUPER_SAMPLING * canvas.getHeight();
List<SubPixel> sampledSubPixels = new ArrayList<>();
for (int i = 0; i < SUPER_SAMPLING; i++) {
for (int j = 0; j < SUPER_SAMPLING; j++) {
Complex z =
camera.toComplex(
((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));
}
}
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, so that the list looks smooth.
*/
record Histogram(double[] breakpoints, 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 {
assert (breakpoints[0] == 0);
assert (breakpoints[breakpoints.length - 1] == 1);
assert (colors.length == breakpoints.length);
}
/**
* 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];
int bpIndex = 0;
for (int ptIndex = 0; ptIndex < howManyPoints; ptIndex++) {
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]);
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;
import java.util.Objects;
public class Main extends Application {
@Override
public void start(Stage primaryStage) throws Exception {
Parent root = FXMLLoader.load(Objects.requireNonNull(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
* sub-pixels. Each sub-pixels has a color, the color of the pixel is the average
* of the sub-pixels' colors.
*/
record Pixel(int x, int y, Collection<SubPixel> 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 sub-pixels for this pixel
*/
Pixel {
}
/**
* @return the list of sub-pixels 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 = 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(x, 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 sub-pixels, 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 sub-pixels 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
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please to comment