Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found
Select Git revision
  • main
  • master
2 results

Target

Select target project
  • b23003912/image-template-tp-4-baron-law-yoann
  • c19028886/tp-3-rayane-chorfi
  • s23026062/image-sahin
  • t21233923/image-template
  • z23012739/image-template
  • a23026569/image-template-amroun-said
  • boukenze.b/image-template-tp-4
  • l3_s3_infoamu/s3/programmation-2/tp-4-manipulation-dimages
  • c22029830/image-template-rafi
  • m22023183/image-template
  • l3_s3_infoamu/s3/programmation-2/image-template
  • a19029459/image-template
  • s21226517/image-template
  • a19021078/image-template
  • alaboure/image-template
  • saddem.r/image-template
  • b21229750/image-template-tp-4
17 results
Select Git revision
  • main
  • master
2 results
Show changes
Commits on Source (12)
Showing
with 490 additions and 789 deletions
...@@ -51,6 +51,7 @@ cmake-build-*/ ...@@ -51,6 +51,7 @@ cmake-build-*/
# IntelliJ # IntelliJ
out/ out/
.idea
# mpeltonen/sbt-idea plugin # mpeltonen/sbt-idea plugin
.idea_modules/ .idea_modules/
......
image: gradle:jdk16 image: openjdk:17-alpine
variables:
GRADLE_OPTS: "-Dorg.gradle.daemon=false"
before_script: before_script:
- export GRADLE_USER_HOME=`pwd`/.gradle - export GRADLE_USER_HOME=`pwd`/.gradle
...@@ -8,11 +11,30 @@ cache: ...@@ -8,11 +11,30 @@ cache:
- .gradle/wrapper - .gradle/wrapper
- .gradle/caches - .gradle/caches
stages:
- build
- test
build:
stage: build
script: ./gradlew --build-cache assemble
cache:
key: "$CI_COMMIT_REF_NAME"
policy: push
paths:
- build
- .gradle
java: java:
stage: test stage: test
script: script: ./gradlew test
- gradle test
artifacts: artifacts:
when: always when: always
reports: reports:
junit: build/test-results/test/**/TEST-*.xml junit: build/test-results/test/**/TEST-*.xml
cache:
key: "$CI_COMMIT_REF_NAME"
policy: pull
paths:
- build
- .gradle
\ No newline at end of file
# Mandelbrot # Image
## Description du projet ## 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. Ce projet est l'occasion de travailler sur la représentation et la manipulation d'images. Ces images seront constituées de pixels caractérisés par une couleur représentant un niveau de gris.
Le but du TP sera de corriger le code de la classe `Complex` en s'aidant de tests unitaires.
## Membres du projet ## Membre du projet
- NOM, prénom, numéro de groupe, du premier participant - NOM, prénom
- NOM, prénom, numéro de groupe, du deuxième participant
plugins { plugins {
id 'java'
id 'application' id 'application'
id "org.openjfx.javafxplugin" version "0.0.10" id "org.openjfx.javafxplugin" version "0.0.14"
} }
group 'M2_BIO_INFO'
version '1.0-SNAPSHOT'
javafx { javafx {
version = "17" version = "20"
modules = [ 'javafx.controls', 'javafx.fxml' ] modules = [ 'javafx.controls', 'javafx.fxml' ]
} }
...@@ -12,16 +16,21 @@ repositories { ...@@ -12,16 +16,21 @@ repositories {
mavenCentral() mavenCentral()
} }
dependencies {
testImplementation('org.junit.jupiter:junit-jupiter-api:5.7.2',
'org.hamcrest:hamcrest-library:2.2', 'net.obvj:junit-utils:1.3.1')
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.7.2'
}
test { test {
useJUnitPlatform() useJUnitPlatform()
} }
dependencies {
testRuntimeOnly("org.junit.platform:junit-platform-launcher") {
because("Only needed to run tests in a version of IntelliJ IDEA that bundles older versions")
}
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine")
testImplementation("org.assertj:assertj-core:3.24.2")
testImplementation("org.junit.jupiter:junit-jupiter:5.10.0")
}
application { application {
mainClassName = "viewer.Main" mainClass.set("Main")
} }
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.1-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-bin.zip
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists
#!/usr/bin/env sh #!/bin/sh
# #
# Copyright 2015 the original author or authors. # Copyright © 2015-2021 the original authors.
# #
# Licensed under the Apache License, Version 2.0 (the "License"); # Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License. # you may not use this file except in compliance with the License.
...@@ -17,67 +17,101 @@ ...@@ -17,67 +17,101 @@
# #
############################################################################## ##############################################################################
## #
## Gradle start up script for UN*X # Gradle start up script for POSIX generated by Gradle.
## #
# Important for running:
#
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
# noncompliant, but you have some other compliant shell such as ksh or
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
# ksh Gradle
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
# * functions;
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
# * compound commands having a testable exit status, especially «case»;
# * various built-in commands including «command», «set», and «ulimit».
#
# Important for patching:
#
# (2) This script targets any POSIX shell, so it avoids extensions provided
# by Bash, Ksh, etc; in particular arrays are avoided.
#
# The "traditional" practice of packing multiple parameters into a
# space-separated string is a well documented source of bugs and security
# problems, so this is (mostly) avoided, by progressively accumulating
# options in "$@", and eventually passing that to Java.
#
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
# see the in-line comments for details.
#
# There are tweaks for specific operating systems such as AIX, CygWin,
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
#
############################################################################## ##############################################################################
# Attempt to set APP_HOME # Attempt to set APP_HOME
# Resolve links: $0 may be a link # Resolve links: $0 may be a link
PRG="$0" app_path=$0
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do # Need this for daisy-chained symlinks.
ls=`ls -ld "$PRG"` while
link=`expr "$ls" : '.*-> \(.*\)$'` APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
if expr "$link" : '/.*' > /dev/null; then [ -h "$app_path" ]
PRG="$link" do
else ls=$( ls -ld "$app_path" )
PRG=`dirname "$PRG"`"/$link" link=${ls#*' -> '}
fi case $link in #(
/*) app_path=$link ;; #(
*) app_path=$APP_HOME$link ;;
esac
done done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_NAME="Gradle" # This is normally unused
APP_BASE_NAME=`basename "$0"` # shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. # 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"' DEFAULT_JVM_OPTS='-Dfile.encoding=UTF-8 "-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value. # Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum" MAX_FD=maximum
warn () { warn () {
echo "$*" echo "$*"
} } >&2
die () { die () {
echo echo
echo "$*" echo "$*"
echo echo
exit 1 exit 1
} } >&2
# OS specific support (must be 'true' or 'false'). # OS specific support (must be 'true' or 'false').
cygwin=false cygwin=false
msys=false msys=false
darwin=false darwin=false
nonstop=false nonstop=false
case "`uname`" in case "$( uname )" in #(
CYGWIN* ) CYGWIN* ) cygwin=true ;; #(
cygwin=true Darwin* ) darwin=true ;; #(
;; MSYS* | MINGW* ) msys=true ;; #(
Darwin* ) NONSTOP* ) nonstop=true ;;
darwin=true
;;
MSYS* | MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
esac esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
...@@ -87,9 +121,9 @@ CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar ...@@ -87,9 +121,9 @@ CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
if [ -n "$JAVA_HOME" ] ; then if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables # IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java" JAVACMD=$JAVA_HOME/jre/sh/java
else else
JAVACMD="$JAVA_HOME/bin/java" JAVACMD=$JAVA_HOME/bin/java
fi fi
if [ ! -x "$JAVACMD" ] ; then if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
...@@ -98,7 +132,7 @@ Please set the JAVA_HOME variable in your environment to match the ...@@ -98,7 +132,7 @@ Please set the JAVA_HOME variable in your environment to match the
location of your Java installation." location of your Java installation."
fi fi
else else
JAVACMD="java" 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. 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 Please set the JAVA_HOME variable in your environment to match the
...@@ -106,80 +140,105 @@ location of your Java installation." ...@@ -106,80 +140,105 @@ location of your Java installation."
fi fi
# Increase the maximum file descriptors if we can. # Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
MAX_FD_LIMIT=`ulimit -H -n` case $MAX_FD in #(
if [ $? -eq 0 ] ; then max*)
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
MAX_FD="$MAX_FD_LIMIT" # shellcheck disable=SC3045
fi MAX_FD=$( ulimit -H -n ) ||
ulimit -n $MAX_FD warn "Could not query maximum file descriptor limit"
if [ $? -ne 0 ] ; then esac
warn "Could not set maximum file descriptor limit: $MAX_FD" case $MAX_FD in #(
fi '' | soft) :;; #(
else *)
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
fi # shellcheck disable=SC3045
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
fi fi
# For Darwin, add options to specify how the application appears in the dock # Collect all arguments for the java command, stacking in reverse order:
if $darwin; then # * args from the command line
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" # * the main class name
fi # * -classpath
# * -D...appname settings
# * --module-path (only if needed)
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
# For Cygwin or MSYS, switch paths to Windows format before running java # For Cygwin or MSYS, switch paths to Windows format before running java
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then if "$cygwin" || "$msys" ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"` APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 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 JAVACMD=$( cygpath --unix "$JAVACMD" )
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else # Now convert the arguments - kludge to limit ourselves to /bin/sh
eval `echo args$i`="\"$arg\"" for arg do
if
case $arg in #(
-*) false ;; # don't mess with options #(
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
[ -e "$t" ] ;; #(
*) false ;;
esac
then
arg=$( cygpath --path --ignore --mixed "$arg" )
fi fi
i=`expr $i + 1` # Roll the args list around exactly as many times as the number of
# args, so each arg winds up back in the position where it started, but
# possibly modified.
#
# NB: a `for` loop captures its iteration list before it begins, so
# changing the positional parameters here affects neither the number of
# iterations, nor the values presented in `arg`.
shift # remove old arg
set -- "$@" "$arg" # push replacement arg
done 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 fi
# Escape application args # Collect all arguments for the java command;
save () { # * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done # shell script including quotes and variable substitutions, so put them in
echo " " # double quotes to make sure that they get re-expanded; and
} # * put everything else in single quotes, so that it's not re-expanded.
APP_ARGS=`save "$@"`
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
org.gradle.wrapper.GradleWrapperMain \
"$@"
# Stop when "xargs" is not available.
if ! command -v xargs >/dev/null 2>&1
then
die "xargs is not available"
fi
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
#
# In Bash we could simply go:
#
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
# set -- "${ARGS[@]}" "$@"
#
# but POSIX shell has neither arrays nor command substitution, so instead we
# post-process each arg (as a line of input to sed) to backslash-escape any
# character that might be a shell metacharacter, then use eval to reverse
# that process (while maintaining the separation between arguments), and wrap
# the whole thing up as a single "set" statement.
#
# This will of course break if any of these variables contains a newline or
# an unmatched quote.
#
# Collect all arguments for the java command, following the shell quoting and substitution rules eval "set -- $(
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
xargs -n1 |
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
tr '\n' ' '
)" '"$@"'
exec "$JAVACMD" "$@" exec "$JAVACMD" "$@"
rootProject.name = 'mandelbrot' rootProject.name = 'image'
import javafx.scene.paint.Color;
/**
* Created by Arnaud Labourel on 02/10/2018.
*/
public class ByteGrayColor implements GrayColor {
private static final int MINIMUM_GRAY_LEVEL = 0;
private static final int MAXIMUM_GRAY_LEVEL = 255;
private static final int OPACITY = 1;
// TODO : add static ByteGrayColors WHITE and BLACK
private final int grayLevel;
public ByteGrayColor(){
this.grayLevel = MINIMUM_GRAY_LEVEL;
}
public ByteGrayColor(int grayLevel) {
// TODO : Corriger l'initialisation de la propriété grayLevel de l'instance.
this.grayLevel = 0;
}
public ByteGrayColor(double luminosity) {
// TODO : Corriger l'initialisation de la propriété grayLevel de l'instance.
this.grayLevel = 0;
}
@Override
public double getLuminosity() {
// TODO : Retourner la luminosité de la couleur (entre 0 noir et 1 blanc)
return 0;
}
@Override
public Color getColor(){
double component = getLuminosity();
return new Color(component, component, component, OPACITY);
}
@Override
public int compareTo(GrayColor o) {
// TODO : Retourner la différence de niveau de gris.
return 0;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (this.getClass() != o.getClass()) return false;
ByteGrayColor color = (ByteGrayColor) o;
return grayLevel == color.grayLevel;
}
}
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.image.PixelWriter;
import java.net.URL;
import java.util.ResourceBundle;
/**
* Created by Arnaud Labourel on 04/10/2018.
*/
public class Display implements Initializable {
@FXML
private Canvas canvas;
MatrixGrayImage image;
@Override
public void initialize(URL location, ResourceBundle resources) {
this.image = MatrixGrayImage.createImageFromPGMFile("images/luminy.pgm");
// TODO : Ajouter les transformations d'image.
render();
}
public void render() {
int pixelWidth = image.getWidth();
int pixelHeight = image.getHeight();
canvas.setWidth(pixelWidth);
canvas.setHeight(pixelHeight);
GraphicsContext graphicsContext = canvas.getGraphicsContext2D();
PixelWriter pixelWriter = graphicsContext.getPixelWriter();
for (int i = 0; i < pixelWidth; i++) {
for (int j = 0; j < pixelHeight; j++) {
renderPixel(i,j, pixelWriter);
}
}
}
private void renderPixel(int x, int y, PixelWriter pixelWriter) {
pixelWriter.setColor(x, y, image.getPixelColor(x, y));
}
}
import javafx.scene.paint.Color;
/**
* Created by Arnaud Labourel on 04/10/2018.
* Interface correspondant à une couleur de gris.
*/
public interface GrayColor extends Comparable<GrayColor> {
double getLuminosity();
Color getColor();
}
/**
* Created by Arnaud Labourel on 04/10/2018.
*/
public interface GrayImage extends Image {
void setPixel(GrayColor gray, int x, int y);
GrayColor getPixelGrayColor(int x, int y);
}
import javafx.scene.paint.Color;
/**
* Created by Arnaud Labourel on 02/10/2018.
*/
public interface Image {
Color getPixelColor(int x, int y);
int getWidth();
int getHeight();
}
package viewer;
import javafx.application.Application; import javafx.application.Application;
import javafx.fxml.FXMLLoader; import javafx.fxml.FXMLLoader;
import javafx.scene.Parent; import javafx.scene.Parent;
import javafx.scene.Scene; import javafx.scene.Scene;
import javafx.stage.Stage; import javafx.stage.Stage;
import java.util.Objects; import java.io.IOException;
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 class Main extends Application
{
public static void main(String[] args) { public static void main(String[] args) {
launch(args); launch(args);
} }
@Override
public void start(Stage primaryStage) throws IOException {
Parent root =FXMLLoader.load(getClass().getResource("fxml/Display.fxml"));
primaryStage.setTitle("Image display");
primaryStage.setScene(new Scene(root));
primaryStage.show();
}
} }
import javafx.scene.paint.Color;
import java.io.FileWriter;
import java.io.InputStream;
import java.io.PrintWriter;
import java.util.Scanner;
/**
* Created by Arnaud Labourel on 02/10/2018.
*/
public class MatrixGrayImage implements GrayImage {
private final GrayColor[][] pixels;
private final int width;
private final int height;
@Override
public void setPixel(GrayColor gray, int x, int y) {
// TODO : Compléter la méthode pour modifier le pixel.
}
@Override
public GrayColor getPixelGrayColor(int x, int y) {
// TODO : Changer les instructions pour retourner le bon pixel.
return new ByteGrayColor();
}
@Override
public Color getPixelColor(int x, int y) {
// TODO : Changer les instructions pour retourner la couleur du pixel.
return Color.WHITE;
}
@Override
public int getWidth() {
// TODO : Changer les instructions pour retourner la largeur de l'image.
return 600;
}
@Override
public int getHeight() {
// TODO : Changer les instructions pour retourner la hauteur de l'image.
return 400;
}
public MatrixGrayImage(int width, int height){
/* TODO : Modifier les instructions pour initialiser correctement
les propriétés de l'instance.
*/
this.width = 0;
this.height = 0;
this.pixels = null;
}
public static MatrixGrayImage createImageFromPGMFile(String fileName) {
// NE PAS MODIFIER !
InputStream file = ClassLoader.getSystemResourceAsStream(fileName);
Scanner scan = new Scanner(file);
scan.nextLine();
scan.nextLine();
int width = scan.nextInt();
int height = scan.nextInt();
MatrixGrayImage result = new MatrixGrayImage(width, height);
scan.nextInt();
for(int y = 0; y < height; y++){
for(int x = 0; x < width; x++) {
GrayColor color = new ByteGrayColor(scan.nextInt());
result.setPixel(color, x, y);
}
}
return result;
}
public void writeIntoPGMFormat(String fileName){
// NE PAS MODIFIER !
try {
FileWriter fileWriter = new FileWriter(fileName);
PrintWriter printWriter = new PrintWriter(fileWriter);
printWriter.println("P2");
printWriter.println("# CREATOR: TP3 Version 1.0");
printWriter.printf("%d %d\n",this.width, this.height);
printWriter.println(pgmCodeOfGrayColor(pixels[0][0]));
for(int y = 0; y < height; y++){
for(int x = 0; x < width; x++) {
printWriter.println(pgmCodeOfGrayColor(getPixelGrayColor(x,y)));
}
}
printWriter.close();
}
catch (Exception e){
e.printStackTrace();
}
}
private static final int PGM_MAXIMUM_CODE = 255;
private int pgmCodeOfGrayColor(GrayColor pixelGrayColor) {
return (int) (pixelGrayColor.getLuminosity() * (double) PGM_MAXIMUM_CODE);
}
}
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);
}
}