commit 2ddced7d419e23d282e0dc714f83155e28120603 Author: Laurent <2-naaturel@users.noreply.gitlab.naaturel.be> Date: Tue Jun 23 20:27:12 2026 +0200 Import from internal git diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1fac4d5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,43 @@ +.gradle +build/ +!gradle/wrapper/gradle-wrapper.jar +!**/src/main/**/build/ +!**/src/test/**/build/ +.kotlin + +### IntelliJ IDEA ### +.idea/modules.xml +.idea/jarRepositories.xml +.idea/compiler.xml +.idea/libraries/ +*.iws +*.iml +*.ipr +out/ +!**/src/main/**/out/ +!**/src/test/**/out/ + +### Eclipse ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache +bin/ +!**/src/main/**/bin/ +!**/src/test/**/bin/ + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ + +### VS Code ### +.vscode/ + +### Mac OS ### +.DS_Store \ No newline at end of file diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..bc44e09 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,12 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml +/discord.xml +/material_theme_project_new.xml +/PMDPlugin.xml +/.name \ No newline at end of file diff --git a/.idea/gradle.xml b/.idea/gradle.xml new file mode 100644 index 0000000..2a65317 --- /dev/null +++ b/.idea/gradle.xml @@ -0,0 +1,17 @@ + + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..f16dea7 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..35eb1dd --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..3fccfec --- /dev/null +++ b/README.md @@ -0,0 +1,86 @@ +# Projet TDS MASI 4 +--- +# Ce qui a changé + +## Fonctionnalité +Certaines fonctionnalités ont été déplacées ou légérement adaptée afin de les rendre +plus facile à prendre en main + +--- + +### Réorganisation des menus +Certains items du menu tels que le chargement et la création des image, +les fonctionnalités de dessin et l'affichage des transformées de Fourier ont été rémaniés. +--- + +### Chargement et création des images +Désormais les images sont automatiquement chargée en format RGB. +Leur réprésentation en niveaux de gris est gardée en mémoire à chaque +modification de l'image et peut-être utilisée dès que nécessaire. + +#### Exemple +```java +//Crée une image blanche de 512 pixels par 512 pixels +Image image = new Image(255,255,255, 512,512); + +//Crée une copie de l'image en récupérant son équivalent en niveaus de gris +GrayScaleMatrix = image.toGrayScale(); +``` + +--- + +### Affichage des transformées de fourier + +Le module, la phase, la partie réelle et la partie imaginaire sont maintenant +régroupés dans une seule et même fenêtre. L'affichage des transformées de plusieurs +images différentes à travers différentes fenêtres reste possible. + +![fourier](./menu_fourier.png) +--- + +## Architecture +C'est ici que la majorité des changements ont eus lieu. +Une architecture et des patterns modernes ont remplacé la majorité +de la base de code précédente. + +### MVP +Le programme utilise maintenant un design pattern architectural +Model-View-Presenter afin de séparer au mieux les responsabilités +des différentes couches et d'imposer un flux d'éxecution strict +tel que décrit comme suit : + +- View : Réceptionne les événements utilisateur et gère l'état visuel de la fenêtre +- Presenter : Couche d'orchestration entre la vue et la logique métier. Possède un accès aux données locales à la vue courante. +- Services : Couche tampons faisant le lien entre les presenters, l'infrastructure et la couche domaine. Possède un accès à l'état global de l'application +- Domaine : Contient la logique métier de l'application. Doit rester totalement agnostique de toute dépendance technologique +- Infrastructure : Couche contenant un accès à des ressources externes telles que le système de fichier de la machine hôte +![architecture](./architecture.png) + +### Gestion d'état +La gestion d'état a été remaniée. Plusieurs composants d'état sont maintenant disponabible dans le package *app.state* : +- Classe abstraite *State* : Classe parent donnant une structure à chaque classe d'état +- Etat global du programme : Représenté par la classe *AppState*. Cet classe est déclarée comme étant un singleton dans le conteneur Guava +- Etat local d'une vue : Representé par toutes les autres classes disponibles dans le package. Ne devrait pas être un singleton + +### Injection de dépendances +Le programme utilise maintenant le conteneur d'injection de dépendance de Google, Guava. +Cet ajout permet d'isoler la logique d'instanciation des différents composants lors de l'éxecution. +La logique de navigation entre les différentes fenêtres s'en retrouve facilitée. +Toutes les dépendances sont enregistrées dans la classe *AppModule* au sein du package *app* + +### Logique de navigation +L'injection de dépendance précédement expliquée a permis d'isoler la logique de navigation. +Désormais, pour naviguer entre différentes vue, il suffit de créer une méthode au sein de +la classe SwingNavigator, d'injecter l'interface INavigator dans une Presenter et d'appeler la méthode nouvellement créée. + +Un exemple de fonctionnement est déjà fourni dans la classe SwingNavigator et peut-être facilement retracé en trouvant +les usages de la méthode *showFourier()* avec un IDE moderne. + +#### Note importante +Toutes les vues doivent être enregistrée en tant que dépendance au sein du conteneur Guava. +Pour ce faire, différents exemples sont déjà implémenté dans la classe *AppModule*. +Cet ajout aura pour conséquence de donner accès à un objet ``Provider`` (où T est le type de la dépendance enregistrée) +qui pourra être injecté dans la classe SwingNavigator. + +Appeler la méthode ``Provider.get()`` aura pour conséquence d'instancier la dépendance désirée, dans notre cas, +une potentielle vue qui pourra ensuite être affichée. diff --git a/architecture.png b/architecture.png new file mode 100644 index 0000000..c1f7d31 Binary files /dev/null and b/architecture.png differ diff --git a/build.gradle.kts b/build.gradle.kts new file mode 100644 index 0000000..d16acd4 --- /dev/null +++ b/build.gradle.kts @@ -0,0 +1,24 @@ +plugins { + id("java") +} + +group = "be.naaturel" +version = "1.0-SNAPSHOT" + +repositories { + mavenCentral() +} + +dependencies { + implementation("org.jfree:jfreechart:1.5.4") + implementation("com.google.inject:guice:7.0.0") + implementation("com.google.guava:guava:33.4.0-jre") + + testImplementation(platform("org.junit:junit-bom:5.10.0")) + testImplementation("org.junit.jupiter:junit-jupiter") + testRuntimeOnly("org.junit.platform:junit-platform-launcher") +} + +tasks.test { + useJUnitPlatform() +} \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..249e583 Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..a6b75b6 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Wed Apr 08 11:32:29 CEST 2026 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100644 index 0000000..1b6c787 --- /dev/null +++ b/gradlew @@ -0,0 +1,234 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original 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 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/master/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 + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit + +APP_NAME="Gradle" +APP_BASE_NAME=${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 "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# 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" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -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 +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + 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 + # 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 +fi + +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# 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. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..107acd3 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,89 @@ +@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 diff --git a/menu_fourier.png b/menu_fourier.png new file mode 100644 index 0000000..6636abe Binary files /dev/null and b/menu_fourier.png differ diff --git a/settings.gradle.kts b/settings.gradle.kts new file mode 100644 index 0000000..d6c967b --- /dev/null +++ b/settings.gradle.kts @@ -0,0 +1 @@ +rootProject.name = "TDS" \ No newline at end of file diff --git a/src/main/java/Main.java b/src/main/java/Main.java new file mode 100644 index 0000000..faf60cb --- /dev/null +++ b/src/main/java/Main.java @@ -0,0 +1,28 @@ +import app.AppModule; +import com.google.inject.Guice; +import com.google.inject.Injector; +import presenters.MainPresenter; +import presenters.NavPresenter; +import ui.implementation.components.nav.NavBar; +import ui.implementation.views.MainView; +import ui.interfaces.IMainView; + +import javax.swing.*; + +/** + * @author Laurent Crema + */ +public class Main { + + public static void main(String[] args) { + Injector injector = Guice.createInjector(new AppModule()); + + SwingUtilities.invokeLater(() -> { + + NavBar navBar = injector.getInstance(NavBar.class); + MainView view = (MainView) injector.getInstance(IMainView.class); + view.setNavBar(navBar); + view.setVisible(true); + }); + } +} diff --git a/src/main/java/app/AppModule.java b/src/main/java/app/AppModule.java new file mode 100644 index 0000000..9eff494 --- /dev/null +++ b/src/main/java/app/AppModule.java @@ -0,0 +1,49 @@ +package app; + +import app.state.AppState; +import app.state.DoubleMatrixState; +import com.google.common.eventbus.EventBus; +import com.google.inject.AbstractModule; +import com.google.inject.Scopes; +import presenters.DoubleMatrixPresenter; +import presenters.MainPresenter; +import presenters.NavPresenter; +import services.ImageService; +import ui.implementation.components.nav.NavBar; +import ui.implementation.views.DoubleMatrix; +import ui.implementation.views.MainView; +import ui.interfaces.IDoubleMatrix; +import ui.interfaces.IMainView; +import ui.interfaces.INavBar; + +/** + * @author Laurent Crema + */ +public class AppModule extends AbstractModule { + + @Override + protected void configure() { + + //--- States --- + bind(AppState.class).in(Scopes.SINGLETON); + bind(DoubleMatrixState.class); + + //--- Application layer --- + bind(EventBus.class).in(Scopes.SINGLETON); + bind(INavigator.class).to(SwingNavigator.class).in(Scopes.SINGLETON); + + //--- UI layer --- + bind(INavBar.class).to(NavBar.class); + bind(IMainView.class).to(MainView.class); + bind(IDoubleMatrix.class).to(DoubleMatrix.class); + + //--- Presentation layer --- + bind(MainPresenter.class); + bind(NavPresenter.class); + bind(DoubleMatrixPresenter.class); + + //--- Service layer layer --- + bind(ImageService.class); + + } +} \ No newline at end of file diff --git a/src/main/java/app/INavigator.java b/src/main/java/app/INavigator.java new file mode 100644 index 0000000..49361e3 --- /dev/null +++ b/src/main/java/app/INavigator.java @@ -0,0 +1,7 @@ +package app; + +public interface INavigator { + + void showFourier(); + +} diff --git a/src/main/java/app/SwingNavigator.java b/src/main/java/app/SwingNavigator.java new file mode 100644 index 0000000..a264057 --- /dev/null +++ b/src/main/java/app/SwingNavigator.java @@ -0,0 +1,23 @@ +package app; + +import com.google.inject.Inject; +import com.google.inject.Provider; +import ui.implementation.views.DoubleMatrix; + +public class SwingNavigator implements INavigator { + + private final Provider doubleMatrixProvider; + + @Inject + public SwingNavigator( + Provider doubleMatrixProvider + ) { + this.doubleMatrixProvider = doubleMatrixProvider; + } + + @Override + public void showFourier() { + DoubleMatrix doubleMatrix = doubleMatrixProvider.get(); + doubleMatrix.setVisible(true); + } +} diff --git a/src/main/java/app/state/AppState.java b/src/main/java/app/state/AppState.java new file mode 100644 index 0000000..4d7e9af --- /dev/null +++ b/src/main/java/app/state/AppState.java @@ -0,0 +1,37 @@ +package app.state; + +import com.google.common.eventbus.EventBus; +import com.google.inject.Inject; +import com.google.inject.Singleton; +import domain.common.Mode; +import domain.events.ImageChangedEvent; +import domain.events.ModeChangedEvent; +import domain.image.Image; + +@Singleton +public class AppState extends State { + + private Mode mode; + private Image rgbImage; + + @Inject + public AppState(EventBus eventBus) { + super(eventBus); + } + + public void setMode(Mode mode) { + this.mode = mode; + this.eventBus.post(new ModeChangedEvent(mode)); + } + + public void setImage(Image image) { + this.rgbImage = image; + this.eventBus.post(new ImageChangedEvent(image)); + } + + public Image getImage(){ + return this.rgbImage; + } + + public Mode getMode() { return this.mode; } +} diff --git a/src/main/java/app/state/DoubleMatrixState.java b/src/main/java/app/state/DoubleMatrixState.java new file mode 100644 index 0000000..71815c6 --- /dev/null +++ b/src/main/java/app/state/DoubleMatrixState.java @@ -0,0 +1,49 @@ +package app.state; + +import com.google.common.eventbus.EventBus; +import com.google.inject.Inject; +import com.google.inject.Singleton; +import domain.events.fourier.*; +import domain.image.GrayScaleMatrix; +import domain.image.processing.complex.ComplexMatrix; + +public class DoubleMatrixState extends State { + + private ComplexMatrix fourier; + private GrayScaleMatrix module; + private GrayScaleMatrix phase; + private GrayScaleMatrix real; + private GrayScaleMatrix imaginary; + + @Inject + public DoubleMatrixState() { super(new EventBus()); } + + public ComplexMatrix getFourier() { + return fourier; + } + + public void setFourier(ComplexMatrix fourier) { + this.fourier = fourier; + this.eventBus.post(new ComplexMatrixChanged(fourier)); + } + + public void setModule(GrayScaleMatrix matrix) { + this.module = matrix; + this.eventBus.post(new ModuleChangedEvent(matrix)); + } + + public void setPhase(GrayScaleMatrix matrix) { + this.phase = matrix; + this.eventBus.post(new PhaseChangedEvent(matrix)); + } + + public void setReal(GrayScaleMatrix matrix) { + this.real = matrix; + this.eventBus.post(new RealChangedEvent(matrix)); + } + + public void setImaginary(GrayScaleMatrix matrix) { + this.imaginary = matrix; + this.eventBus.post(new ImaginaryChangedEvent(matrix)); + } +} \ No newline at end of file diff --git a/src/main/java/app/state/State.java b/src/main/java/app/state/State.java new file mode 100644 index 0000000..cded0ad --- /dev/null +++ b/src/main/java/app/state/State.java @@ -0,0 +1,16 @@ +package app.state; + +import com.google.common.eventbus.EventBus; + +public abstract class State { + + protected EventBus eventBus; + + public State(EventBus eventBus) { + this.eventBus = eventBus; + } + + public void register(Object obj) { + eventBus.register(obj); + } +} diff --git a/src/main/java/domain/common/Mode.java b/src/main/java/domain/common/Mode.java new file mode 100644 index 0000000..f81bb93 --- /dev/null +++ b/src/main/java/domain/common/Mode.java @@ -0,0 +1,9 @@ +package domain.common; + +public enum Mode { + DISABLED, + DRAW_PIXEL, + DRAW_LINE, + DRAW_RECTANGLE, + DRAW_CIRCLE, +} diff --git a/src/main/java/domain/events/ImageChangedEvent.java b/src/main/java/domain/events/ImageChangedEvent.java new file mode 100644 index 0000000..124fb89 --- /dev/null +++ b/src/main/java/domain/events/ImageChangedEvent.java @@ -0,0 +1,5 @@ +package domain.events; + +import domain.image.Image; + +public record ImageChangedEvent(Image image) { } diff --git a/src/main/java/domain/events/MatrixChangedEvent.java b/src/main/java/domain/events/MatrixChangedEvent.java new file mode 100644 index 0000000..997e5c9 --- /dev/null +++ b/src/main/java/domain/events/MatrixChangedEvent.java @@ -0,0 +1,6 @@ +package domain.events; + +import domain.image.GrayScaleMatrix; + +public record MatrixChangedEvent(GrayScaleMatrix matrix) { +} diff --git a/src/main/java/domain/events/ModeChangedEvent.java b/src/main/java/domain/events/ModeChangedEvent.java new file mode 100644 index 0000000..ed93ed3 --- /dev/null +++ b/src/main/java/domain/events/ModeChangedEvent.java @@ -0,0 +1,5 @@ +package domain.events; + +import domain.common.Mode; + +public record ModeChangedEvent(Mode mode) { } diff --git a/src/main/java/domain/events/fourier/ComplexMatrixChanged.java b/src/main/java/domain/events/fourier/ComplexMatrixChanged.java new file mode 100644 index 0000000..4cd7c45 --- /dev/null +++ b/src/main/java/domain/events/fourier/ComplexMatrixChanged.java @@ -0,0 +1,6 @@ +package domain.events.fourier; + +import domain.image.processing.complex.ComplexMatrix; + +public record ComplexMatrixChanged(ComplexMatrix matrix) { +} diff --git a/src/main/java/domain/events/fourier/ImaginaryChangedEvent.java b/src/main/java/domain/events/fourier/ImaginaryChangedEvent.java new file mode 100644 index 0000000..dc980d3 --- /dev/null +++ b/src/main/java/domain/events/fourier/ImaginaryChangedEvent.java @@ -0,0 +1,6 @@ +package domain.events.fourier; + +import domain.image.GrayScaleMatrix; + +public record ImaginaryChangedEvent(GrayScaleMatrix matrix) { +} diff --git a/src/main/java/domain/events/fourier/ModuleChangedEvent.java b/src/main/java/domain/events/fourier/ModuleChangedEvent.java new file mode 100644 index 0000000..e9c7c56 --- /dev/null +++ b/src/main/java/domain/events/fourier/ModuleChangedEvent.java @@ -0,0 +1,6 @@ +package domain.events.fourier; + +import domain.image.GrayScaleMatrix; + +public record ModuleChangedEvent(GrayScaleMatrix matrix) { +} diff --git a/src/main/java/domain/events/fourier/PhaseChangedEvent.java b/src/main/java/domain/events/fourier/PhaseChangedEvent.java new file mode 100644 index 0000000..4192da8 --- /dev/null +++ b/src/main/java/domain/events/fourier/PhaseChangedEvent.java @@ -0,0 +1,6 @@ +package domain.events.fourier; + +import domain.image.GrayScaleMatrix; + +public record PhaseChangedEvent(GrayScaleMatrix matrix) { +} diff --git a/src/main/java/domain/events/fourier/RealChangedEvent.java b/src/main/java/domain/events/fourier/RealChangedEvent.java new file mode 100644 index 0000000..9e97e81 --- /dev/null +++ b/src/main/java/domain/events/fourier/RealChangedEvent.java @@ -0,0 +1,6 @@ +package domain.events.fourier; + +import domain.image.GrayScaleMatrix; + +public record RealChangedEvent(GrayScaleMatrix matrix) { +} diff --git a/src/main/java/domain/image/GrayScaleMatrix.java b/src/main/java/domain/image/GrayScaleMatrix.java new file mode 100644 index 0000000..b40b0a3 --- /dev/null +++ b/src/main/java/domain/image/GrayScaleMatrix.java @@ -0,0 +1,130 @@ +package domain.image; + +import infrastructure.image.io.ImageSaver; + +import java.util.Arrays; + +/** + * @author Jean-Marc Wagner, Laurent Crema + */ +public class GrayScaleMatrix { + + private final double[][] matrix; + + private final double maxValue; + private final double minValue; + + public GrayScaleMatrix(double[][] matrix) { + this.matrix = matrix; + this.maxValue = computeMax(); + this.minValue = computeMin(); + } + + /** + * Returns a copy of the actual content of this matrix. + * Since a copy is returned, no change that will be applied on it is going to affect the original matrix + * @return a copy of the actual content + */ + public double[][] getRawData() { + return Arrays.copyOf(matrix, matrix.length); + } + + public int getWidth(){ + return matrix[0].length; + } + + public int getHeight(){ + return matrix.length; + } + + public double getMaxValue() { + return maxValue; + } + + public double getMinValue() { + return minValue; + } + + public double getValueAt(double normalized) { + if(normalized <= 0) { return minValue; } + if(normalized >= 1) { return maxValue; } + return minValue + (maxValue - minValue) * normalized; + } + + private double computeMax(){ + double res = matrix[0][0]; + for(int y = 0; y < getHeight(); y++) { + for (int x = 0; x < getWidth(); x++) { + double value = matrix[y][x]; + if (value > res) res = matrix[y][x]; + } + } + return res; + } + + private double computeMin(){ + double res = matrix[0][0]; + for(int y = 0; y < getHeight(); y++) { + for (int x = 0; x < getWidth(); x++) { + double value = matrix[y][x]; + if (value < res) res = matrix[y][x]; + } + } + return res; + } + + public GrayScaleMatrix clip() { + return clip(minValue, maxValue); + } + + public GrayScaleMatrix clip(double lowerBound, double upperBound) { + + int height = getHeight(); + int width = getWidth(); + + double[][] res = new double[height][width]; + + double range = upperBound - lowerBound; + if (range == 0) range = 1; + + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + + double v = matrix[y][x]; + double out; + + if (v >= upperBound) { + out = 255; + } else if (v <= lowerBound) { + out = 0; + } else { + out = (v - lowerBound) / range * 255.0; + } + + res[y][x] = Math.clamp(out, 0, 255); + } + } + + return new GrayScaleMatrix(res); + } + + public Image toImage() { + + Pixel[][] res = new Pixel[getHeight()][getWidth()]; + + double range = maxValue - minValue; + if (range == 0) range = 1; + + for (int y = 0; y < getHeight(); y++) { + for (int x = 0; x < getWidth(); x++) { + + double normalized = (matrix[y][x] - minValue) / range; + int gray = (int) (normalized * 255); + + res[y][x] = new Pixel(gray, gray, gray); + } + } + + return new Image(res); + } +} diff --git a/src/main/java/domain/image/Image.java b/src/main/java/domain/image/Image.java new file mode 100644 index 0000000..db2bb86 --- /dev/null +++ b/src/main/java/domain/image/Image.java @@ -0,0 +1,126 @@ +package domain.image; + +/** + * @author Laurent Crema + */ +public class Image { + + private final Pixel[][] pixels; + private GrayScaleMatrix grayScaleEquivalent; + + public Image(Pixel[][] pixels) { + if(pixels == null || pixels.length == 0 || pixels[0].length == 0) + throw new IllegalArgumentException("Invalid dimensions"); + this.pixels = pixels; + } + + public Image(int red, int green, int blue, int height, int width) { + pixels = new Pixel[height][width]; + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + pixels[y][x] = new Pixel(red, green, blue); + } + } + } + + public int getWidth(){ + return pixels[0].length; + } + + public int getHeight(){ + return pixels.length; + } + + public Pixel getPixel(int x, int y){ + if(x < 0 || y < 0 || x >= getWidth() || y >= getHeight()) + throw new IllegalArgumentException("Invalid coordinates"); + return pixels[y][x]; + } + + public void setPixelColor(int x, int y, int red, int green, int blue){ + if(x < 0 || y < 0 || x >= getWidth() || y >= getHeight()) + throw new IllegalArgumentException("Invalid coordinates"); + Pixel p = new Pixel(red, green, blue); + pixels[y][x] = p; + grayScaleEquivalent = toGrayScale(); + } + + public void setRectangleColor(int x1, int x2, int y1, int y2, + int red, int green, int blue) { + + int minX = Math.max(0, Math.min(x1, x2)); + int maxX = Math.min(pixels[0].length - 1, Math.max(x1, x2)); + + int minY = Math.max(0, Math.min(y1, y2)); + int maxY = Math.min(pixels.length - 1, Math.max(y1, y2)); + + Pixel p = new Pixel(red, green, blue); + + for (int y = minY; y <= maxY; y++) { + for (int x = minX; x <= maxX; x++) { + pixels[y][x] = p; + } + } + + grayScaleEquivalent = toGrayScale(); + } + + public void setLineColor(int x1, int x2, int y1, int y2, + int red, int green, int blue) { + + int dx = Math.abs(x2 - x1); + int dy = Math.abs(y2 - y1); + + int sx = x1 < x2 ? 1 : -1; + int sy = y1 < y2 ? 1 : -1; + + int err = dx - dy; + + Pixel p = new Pixel(red, green, blue); + + while (true) { + if (x1 >= 0 && y1 >= 0 && x1 < getWidth() && y1 < getHeight()) { + pixels[y1][x1] = p; + } + + if (x1 == x2 && y1 == y2) break; + + int e2 = 2 * err; + + if (e2 > -dy) { + err -= dy; + x1 += sx; + } + + if (e2 < dx) { + err += dx; + y1 += sy; + } + } + + grayScaleEquivalent = toGrayScale(); + } + + /** + * Return the corresponding gray scale matrix. Since this operation is destructive, the original image is not modified. + * Instead, a new one is returned. + * @return A newly converted image + */ + public GrayScaleMatrix toGrayScale() { + + if(grayScaleEquivalent != null) return this.grayScaleEquivalent; + + double[][] res = new double[getHeight()][getWidth()]; + + for (int y = 0; y < getHeight(); y++) { + for (int x = 0; x < getWidth(); x++) { + + Pixel p = pixels[y][x]; + res[y][x] = p.grayValue(); + } + } + + this.grayScaleEquivalent = new GrayScaleMatrix(res); + return this.grayScaleEquivalent; + } +} diff --git a/src/main/java/domain/image/Pixel.java b/src/main/java/domain/image/Pixel.java new file mode 100644 index 0000000..5af15f6 --- /dev/null +++ b/src/main/java/domain/image/Pixel.java @@ -0,0 +1,62 @@ +package domain.image; + +/** + * @author Laurent Crema + */ +public class Pixel { + + private int red, green, blue; + + public Pixel(int red, int green, int blue) { + + if(checkColorValidity(red)) + throw new IllegalArgumentException("Invalid red value"); // <-- here + if(checkColorValidity(green)) throw new IllegalArgumentException("Invalid green value"); + if(checkColorValidity(blue)) throw new IllegalArgumentException("Invalid blue value"); + + this.red = red; + this.green = green; + this.blue = blue; + } + + public int getRed() { + return red; + } + + public void setRed(int value) { + if(checkColorValidity(value)) throw new IllegalArgumentException("Invalid value"); + this.red = value; + } + + public int getGreen() { + return green; + } + + public void setGreen(int value) { + if(checkColorValidity(value)) throw new IllegalArgumentException("Invalid value"); + this.green = value; + } + + public int getBlue() { + return blue; + } + + public void setBlue(int value) { + if(checkColorValidity(value)) throw new IllegalArgumentException("Invalid value"); + this.blue = value; + } + + public boolean checkColorValidity(int color) { + return !(color >= 0 && color <= 255); + } + + /** + * Convert the current RGB combination into a gray value according to UIT-R BT 709 standard. + * The current RGB values are not modified. + * @return the corresponding gray scale value + */ + public int grayValue(){ + return (int) (0.2126 * red + 0.7152 * green + 0.0722 * blue); + } + +} diff --git a/src/main/java/domain/image/processing/complex/ComplexMatrix.java b/src/main/java/domain/image/processing/complex/ComplexMatrix.java new file mode 100644 index 0000000..f45b103 --- /dev/null +++ b/src/main/java/domain/image/processing/complex/ComplexMatrix.java @@ -0,0 +1,80 @@ +package domain.image.processing.complex; + +public class ComplexMatrix +{ + private Complexe m[][]; + private int lignes; + private int colonnes; + + /** Creates a new instance of MatriceComplexe */ + public ComplexMatrix(int l, int c) + { + lignes = l; + colonnes = c; + m = new Complexe[l][c]; + for(int i=0 ; i v ********** + double pr1[][] = new double[M][N]; + double pi1[][] = new double[M][N]; + + for(int m=0 ; m 0) + { + pr1[m][N-v] = pr1[m][v]; + pi1[m][N-v] = -pi1[m][v]; + } + } + } + + //********** m --> u ********** + double pr2[][] = new double[M][N]; + double pi2[][] = new double[M][N]; + + for(int v=0 ; v n ********** + double pr1[][] = new double[M][N]; + double pi1[][] = new double[M][N]; + + for(int u=0 ; u m ********** + double pr2[][] = new double[M][N]; + double pi2[][] = new double[M][N]; + + for(int n=0 ; n= 0) && (pixelGrayValue <= 255)) { + histo[pixelGrayValue]++; + } + } + } + return histo; + } +} diff --git a/src/main/java/domain/image/processing/lineaire/FiltrageLinaireGlobal.java b/src/main/java/domain/image/processing/lineaire/FiltrageLinaireGlobal.java new file mode 100644 index 0000000..14c95ea --- /dev/null +++ b/src/main/java/domain/image/processing/lineaire/FiltrageLinaireGlobal.java @@ -0,0 +1,20 @@ +package domain.image.processing.lineaire; + +import jdk.jshell.spi.ExecutionControl; + +public class FiltrageLinaireGlobal { + + public static int[][] filtrePasseBasIdeal(int[][] image,int frequenceCoupure){ + return null; + } + public static int[][] filtrePasseHautIdeal(int[][] image,int frequenceCoupure){ + return null; + } + public static int[][] filtrePasseBasButterworth(int[][] image,int frequenceCoupure,int ordre){ + return null; + } + public static int[][] filtrePasseHautButterworth(int[][] image,int frequenceCoupure, int ordre) { + return null; + } + +} diff --git a/src/main/java/infrastructure/image/io/ImageLoader.java b/src/main/java/infrastructure/image/io/ImageLoader.java new file mode 100644 index 0000000..259b427 --- /dev/null +++ b/src/main/java/infrastructure/image/io/ImageLoader.java @@ -0,0 +1,39 @@ +package infrastructure.image.io; + +import javax.imageio.ImageIO; +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.IOException; + +import domain.image.Image; +import domain.image.Pixel; + +public class ImageLoader { + + public static Image loadImage(File f) throws IOException { + + BufferedImage buffered = ImageIO.read(f); + + if (buffered == null) throw new IOException("Unsupported or invalid image file: " + f); + + int width = buffered.getWidth(); + int height = buffered.getHeight(); + + Pixel[][] pixels = new Pixel[height][width]; + + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + + int rgb = buffered.getRGB(x, y); + + int r = (rgb >> 16) & 0xFF; + int g = (rgb >> 8) & 0xFF; + int b = rgb & 0xFF; + + pixels[y][x] = new Pixel(r, g, b); + } + } + + return new Image(pixels); + } +} \ No newline at end of file diff --git a/src/main/java/infrastructure/image/io/ImageSaver.java b/src/main/java/infrastructure/image/io/ImageSaver.java new file mode 100644 index 0000000..fae14b0 --- /dev/null +++ b/src/main/java/infrastructure/image/io/ImageSaver.java @@ -0,0 +1,12 @@ +package infrastructure.image.io; + +import domain.image.Image; + +import java.io.IOException; + +public class ImageSaver { + + public static void saveImage(Image image) throws IOException { + } + +} diff --git a/src/main/java/infrastructure/ui/ImageMapper.java b/src/main/java/infrastructure/ui/ImageMapper.java new file mode 100644 index 0000000..7eb2245 --- /dev/null +++ b/src/main/java/infrastructure/ui/ImageMapper.java @@ -0,0 +1,55 @@ +package infrastructure.ui; + +import domain.image.Image; +import domain.image.Pixel; + +import java.awt.image.BufferedImage; + +public final class ImageMapper { + + public static BufferedImage toBufferedImage(Image image) { + + int width = image.getWidth(); + int height = image.getHeight(); + + BufferedImage out = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); + + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + + Pixel p = image.getPixel(x, y); + + int rgb = (p.getRed() << 16) + | (p.getGreen() << 8) + | p.getBlue(); + + out.setRGB(x, y, rgb); + } + } + + return out; + } + + public static Image fromBufferedImage(BufferedImage img) { + + int width = img.getWidth(); + int height = img.getHeight(); + + Pixel[][] pixels = new Pixel[height][width]; + + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + + int rgb = img.getRGB(x, y); + + int r = (rgb >> 16) & 0xFF; + int g = (rgb >> 8) & 0xFF; + int b = rgb & 0xFF; + + pixels[y][x] = new Pixel(r, g, b); + } + } + + return new Image(pixels); + } +} diff --git a/src/main/java/presenters/DoubleMatrixPresenter.java b/src/main/java/presenters/DoubleMatrixPresenter.java new file mode 100644 index 0000000..b21a629 --- /dev/null +++ b/src/main/java/presenters/DoubleMatrixPresenter.java @@ -0,0 +1,126 @@ +package presenters; + +import app.state.DoubleMatrixState; +import com.google.common.eventbus.Subscribe; +import com.google.inject.Inject; +import domain.events.fourier.ImaginaryChangedEvent; +import domain.events.fourier.ModuleChangedEvent; +import domain.events.fourier.PhaseChangedEvent; +import domain.events.fourier.RealChangedEvent; +import domain.image.GrayScaleMatrix; +import domain.image.Image; +import domain.image.processing.complex.ComplexMatrix; +import services.ImageService; +import ui.interfaces.IDoubleMatrix; + +import java.io.IOException; + +/** + * @author Laurent Crema + */ +public class DoubleMatrixPresenter { + + private IDoubleMatrix view; + private final DoubleMatrixState state; + private final ImageService imageService; + + @Inject + public DoubleMatrixPresenter(ImageService imageService, DoubleMatrixState state) { + this.imageService = imageService; + this.state = state; + this.state.register(this); + } + + public void setView(IDoubleMatrix view){ + this.view = view; + } + + public void saveImage(Image image) throws IOException { + this.imageService.saveImage(image); + } + + public void loadModule(){ + double[][] module = getFourier().getModule(); + state.setModule(new GrayScaleMatrix(module)); + } + + public void loadPhase(){ + double[][] phase = getFourier().getPhase(); + state.setPhase(new GrayScaleMatrix(phase)); + } + + public void loadReal(){ + double[][] real = getFourier().getPartieReelle(); + state.setReal(new GrayScaleMatrix(real)); + } + + public void loadImaginary(){ + double[][] imaginary = getFourier().getPartieImaginaire(); + state.setImaginary(new GrayScaleMatrix(imaginary)); + } + + public double getValueAt(GrayScaleMatrix matrix, double normalized){ + return matrix.getValueAt(normalized); + } + + public void clipModule(double black, double white){ + GrayScaleMatrix module = new GrayScaleMatrix(getFourier().getModule()); + GrayScaleMatrix matrix = clip(module, black, white); + state.setModule(matrix); + } + + public void clipPhase(double black, double white){ + GrayScaleMatrix phase = new GrayScaleMatrix(getFourier().getPhase()); + GrayScaleMatrix matrix = clip(phase, black, white); + state.setPhase(matrix); + } + + public void clipReal(double black, double white){ + GrayScaleMatrix real = new GrayScaleMatrix(getFourier().getPartieReelle()); + GrayScaleMatrix matrix = clip(real, black, white); + state.setReal(matrix); + } + + public void clipImaginary(double black, double white){ + GrayScaleMatrix imaginary = new GrayScaleMatrix(getFourier().getPartieImaginaire()); + GrayScaleMatrix matrix = clip(imaginary, black, white); + state.setImaginary(matrix); + } + + @Subscribe + public void onModuleUpdate(ModuleChangedEvent event) { + GrayScaleMatrix module = new GrayScaleMatrix(getFourier().getModule()); + view.updateModule(module, event.matrix()); + } + + @Subscribe + public void onPhaseUpdate(PhaseChangedEvent event) { + GrayScaleMatrix phase = new GrayScaleMatrix(getFourier().getPhase()); + view.updatePhase(phase, event.matrix()); + } + + @Subscribe + public void onRealUpdate(RealChangedEvent event) { + GrayScaleMatrix real = new GrayScaleMatrix(getFourier().getPartieReelle()); + view.updateReal(real, event.matrix()); + } + + @Subscribe + public void onImaginaryUpdate(ImaginaryChangedEvent event) { + GrayScaleMatrix imaginary = new GrayScaleMatrix(getFourier().getPartieImaginaire()); + view.updateImaginary(imaginary, event.matrix()); + } + + private GrayScaleMatrix clip(GrayScaleMatrix matrix, double black, double white){ + double lowerBound = matrix.getValueAt(black); + double upperBound = matrix.getValueAt(white); + return matrix.clip(lowerBound, upperBound); + } + + private ComplexMatrix getFourier() { + if (state.getFourier() == null) { + state.setFourier(imageService.computeFourier()); + } + return state.getFourier(); + } +} diff --git a/src/main/java/presenters/MainPresenter.java b/src/main/java/presenters/MainPresenter.java new file mode 100644 index 0000000..ca33501 --- /dev/null +++ b/src/main/java/presenters/MainPresenter.java @@ -0,0 +1,67 @@ +package presenters; + +import com.google.common.eventbus.EventBus; +import com.google.common.eventbus.Subscribe; +import com.google.inject.Inject; +import domain.common.Mode; +import domain.events.ImageChangedEvent; +import domain.events.ModeChangedEvent; +import domain.image.Image; +import services.ImageService; +import services.ModeService; +import ui.interfaces.IMainView; + +/** + * @author Laurent Crema + */ +public class MainPresenter { + + private IMainView view; + private final ModeService modeService; + private final ImageService imageService; + + @Inject + public MainPresenter(ModeService modeService, ImageService imageService, EventBus eventBus) { + this.modeService = modeService; + this.imageService = imageService; + eventBus.register(this); + } + + public void setView(IMainView view) { + this.view = view; + } + + @Subscribe + public void onImageChanged(ImageChangedEvent e){ + view.displayImage(e.image()); + } + + @Subscribe + public void onModeChanged(ModeChangedEvent e){ + view.changeMode(e.mode()); + } + + public void drawPixel(int x, int y, int red, int green, int blue){ + if(modeService.getMode() != Mode.DRAW_PIXEL) return; + Image image = imageService.getImage(); + image.setPixelColor(x, y, red, green, blue); + imageService.setImage(image); + } + + public void drawShape(int x1, int x2, int y1, int y2, int red, int green, int blue){ + Image image = imageService.getImage(); + switch (modeService.getMode()) { + case DRAW_RECTANGLE: + image.setRectangleColor(x1, x2, y1, y2, red, green, blue); + break; + case DRAW_LINE: + image.setLineColor(x1, x2, y1, y2, red, green, blue); + break; + case DRAW_CIRCLE: + break; + } + imageService.setImage(image); + } + + +} diff --git a/src/main/java/presenters/NavPresenter.java b/src/main/java/presenters/NavPresenter.java new file mode 100644 index 0000000..9d1162b --- /dev/null +++ b/src/main/java/presenters/NavPresenter.java @@ -0,0 +1,55 @@ +package presenters; + +import app.INavigator; +import com.google.inject.Inject; +import domain.common.Mode; +import domain.image.GrayScaleMatrix; +import services.ImageService; +import services.ModeService; +import ui.interfaces.INavBar; + +import java.io.File; +import java.io.IOException; + +/** + * @author Laurent Crema + */ +public class NavPresenter { + + private INavBar navBar; + private INavigator navigator; + private final ImageService imageService; + private final ModeService modeService; + + @Inject + public NavPresenter(INavigator navigator, ImageService imageService, ModeService modeService) { + this.navigator = navigator; + this.imageService = imageService; + this.modeService = modeService; + } + + public void setView(INavBar navBar) { + this.navBar = navBar; + } + + public void createImage(int red, int green, int blue, int height, int width) { + imageService.createImage(red, green, blue, height, width); + } + + public void loadImage(File f) throws IOException { + imageService.loadImage(f); + } + + public void goToFourier(){ + navigator.showFourier(); + } + + public GrayScaleMatrix getImageGrayMatrix() { + //return ((CImageNG)appState.getImage()).getMatrice(); + return imageService.getImage().toGrayScale(); + } + + public void setMode(Mode mode) { + modeService.setMode(mode); + } +} diff --git a/src/main/java/services/ImageService.java b/src/main/java/services/ImageService.java new file mode 100644 index 0000000..f9a7f2b --- /dev/null +++ b/src/main/java/services/ImageService.java @@ -0,0 +1,55 @@ +package services; + +import app.state.AppState; +import com.google.inject.Inject; +import domain.image.Image; +import domain.image.GrayScaleMatrix; +import domain.image.processing.complex.ComplexMatrix; +import domain.image.processing.fourier.Fourier; +import infrastructure.image.io.ImageLoader; +import infrastructure.image.io.ImageSaver; + +import java.io.File; +import java.io.IOException; + +public class ImageService { + + private final AppState appState; + + @Inject + public ImageService(AppState appState) { + this.appState = appState; + } + + public void createImage(int red, int green, int blue, int height, int width) { + Image image = new Image(red, green, blue, height, width); + appState.setImage(image); + } + + public void loadImage(File f) throws IOException { + Image image = ImageLoader.loadImage(f); + appState.setImage(image); + } + + public void saveImage(Image image) throws IOException { + ImageSaver.saveImage(image); + } + + public ComplexMatrix computeFourier(){ + GrayScaleMatrix matrix = appState.getImage().toGrayScale(); + ComplexMatrix fourier = Fourier.Fourier2D(matrix.getRawData()); + return Fourier.decroise(fourier); + } + + public Image getImage(){ + return appState.getImage(); + } + + public void setImage(Image img){ + appState.setImage(img); + } + + public GrayScaleMatrix getGrayScale(){ + return appState.getImage().toGrayScale(); + } +} diff --git a/src/main/java/services/ModeService.java b/src/main/java/services/ModeService.java new file mode 100644 index 0000000..922bc7a --- /dev/null +++ b/src/main/java/services/ModeService.java @@ -0,0 +1,22 @@ +package services; + +import app.state.AppState; +import com.google.inject.Inject; +import domain.common.Mode; + +public class ModeService { + + private final AppState appState; + + @Inject + public ModeService(AppState appState) { + this.appState = appState; + } + + public void setMode(Mode m){ + appState.setMode(m); + } + + public Mode getMode() { return this.appState.getMode(); } + +} diff --git a/src/main/java/ui/implementation/components/image/ImagePanel.java b/src/main/java/ui/implementation/components/image/ImagePanel.java new file mode 100644 index 0000000..28e1434 --- /dev/null +++ b/src/main/java/ui/implementation/components/image/ImagePanel.java @@ -0,0 +1,79 @@ +package ui.implementation.components.image; + +import domain.image.GrayScaleMatrix; +import domain.image.Image; +import infrastructure.ui.ImageMapper; + +import javax.swing.*; +import java.awt.*; +import java.awt.image.BufferedImage; + + +public class ImagePanel extends JPanel { + private BufferedImage image; + private double scale; + private int drawW; + private int drawH; + private int offsetX; + private int offsetY; + + public ImagePanel() { } + + public ImagePanel(BufferedImage image) { + loadImage(image); + } + + public void loadImage(BufferedImage image){ + this.image = image; + setPreferredSize(new Dimension(image.getWidth(null), image.getHeight(null))); + repaint(); + } + + public void loadImage(GrayScaleMatrix matrix){ + this.image = ImageMapper.toBufferedImage(matrix.toImage()); + setPreferredSize(new Dimension(this.image.getWidth(null), this.image.getHeight(null))); + repaint(); + } + + public Point toImageCoordinates(int x, int y) { + if (image == null) return null; + + if (x < offsetX || x >= offsetX + drawW || + y < offsetY || y >= offsetY + drawH) { + return null; + } + + int imgX = (int) ((x - offsetX) / scale); + int imgY = (int) ((y - offsetY) / scale); + + return new Point(imgX, imgY); + } + + private void computeViewport() { + if (image == null) return; + + int imgW = image.getWidth(); + int imgH = image.getHeight(); + + scale = Math.min( + (double) getWidth() / imgW, + (double) getHeight() / imgH + ); + + drawW = (int) (imgW * scale); + drawH = (int) (imgH * scale); + + offsetX = (getWidth() - drawW) / 2; + offsetY = (getHeight() - drawH) / 2; + } + + + + @Override + protected void paintComponent(Graphics g) { + super.paintComponent(g); + if (image == null) return; + computeViewport(); + g.drawImage(image, offsetX, offsetY, drawW, drawH, null); + } +} diff --git a/src/main/java/ui/implementation/components/intent/FileChooser.java b/src/main/java/ui/implementation/components/intent/FileChooser.java new file mode 100644 index 0000000..dfcea99 --- /dev/null +++ b/src/main/java/ui/implementation/components/intent/FileChooser.java @@ -0,0 +1,9 @@ +package ui.implementation.components.intent; + +import javax.swing.*; +public class FileChooser extends JFileChooser { + + public FileChooser(String initialLocation) { + super(initialLocation); + } +} diff --git a/src/main/java/ui/implementation/components/nav/Menu.java b/src/main/java/ui/implementation/components/nav/Menu.java new file mode 100644 index 0000000..4b5e58b --- /dev/null +++ b/src/main/java/ui/implementation/components/nav/Menu.java @@ -0,0 +1,32 @@ +package ui.implementation.components.nav; + +import javax.swing.*; +import java.net.URL; + +public class Menu extends JMenu { + + public Menu(String title, JMenuItem... items) + { + this(title,null, items); + } + + public Menu(String title, String iconPath, JMenuItem... items) + { + super(title); + + if (iconPath != null) { + URL url = getClass().getResource(iconPath); + if (url != null) { + setIcon(new ImageIcon(url)); + } + } + + if (items != null) { + for (JMenuItem item : items) { + if (item != null) { + this.add(item); + } + } + } + } +} diff --git a/src/main/java/ui/implementation/components/nav/MenuItem.java b/src/main/java/ui/implementation/components/nav/MenuItem.java new file mode 100644 index 0000000..6277f52 --- /dev/null +++ b/src/main/java/ui/implementation/components/nav/MenuItem.java @@ -0,0 +1,25 @@ +package ui.implementation.components.nav; + +import javax.swing.*; +import java.awt.event.ActionListener; +import java.net.URL; + +public class MenuItem extends JMenuItem { + + public MenuItem(String title, ActionListener listener) { + this(title, null, listener); + } + + public MenuItem(String title, String iconPath, ActionListener listener) { + super(title); + if(iconPath != null) { + URL url = getClass().getResource(iconPath); + if (url != null) { + super.setIcon(new ImageIcon(url)); + } + } + if (listener != null) { + addActionListener(listener); + } + } +} diff --git a/src/main/java/ui/implementation/components/nav/NavBar.java b/src/main/java/ui/implementation/components/nav/NavBar.java new file mode 100644 index 0000000..6b14a40 --- /dev/null +++ b/src/main/java/ui/implementation/components/nav/NavBar.java @@ -0,0 +1,301 @@ +package ui.implementation.components.nav; + +import domain.image.GrayScaleMatrix; +import domain.image.processing.histogram.Histogramme; +import domain.common.Mode; +import jakarta.inject.Inject; +import org.jfree.chart.ChartFactory; +import org.jfree.chart.ChartFrame; +import org.jfree.chart.JFreeChart; +import org.jfree.chart.axis.ValueAxis; +import org.jfree.chart.plot.PlotOrientation; +import org.jfree.chart.plot.XYPlot; +import org.jfree.data.xy.XYSeries; +import org.jfree.data.xy.XYSeriesCollection; +import presenters.NavPresenter; +import ui.implementation.components.intent.FileChooser; +import ui.implementation.dialogs.ImageCreatorDialog; +import ui.implementation.dialogs.GreyScaleImageCreatorDialog; +import ui.implementation.dialogs.RGBImageCreatorDialog; +import ui.interfaces.INavBar; + +import javax.swing.*; +import java.awt.event.ActionEvent; +import java.io.File; +import java.io.IOException; + +/** + * @author Jean-Marc Wagner, Laurent Crema + */ +public class NavBar extends JMenuBar implements INavBar { + + private JMenu imageMenu; + private JMenu drawingMenu; + private JMenu fourierMenu; + private JMenu linearMenu; + private JMenu histogramMenu; + + private NavPresenter presenter; + + @Inject + public NavBar(NavPresenter presenter) { + this.presenter = presenter; + this.presenter.setView(this); + initComponents(); + } + + private void initComponents(){ + + imageMenu = new Menu("Image","/net_13_p1.jpg", + new Menu("Nouvelle", "/file_65_p3.jpg", + new MenuItem("RGB", e -> this.createImage(e, new RGBImageCreatorDialog(new JFrame(),true))), + new MenuItem("NG", e -> this.createImage(e, new GreyScaleImageCreatorDialog(new JFrame(),true))) + ), + new MenuItem("Ouvrir...", "/folder_036_p3.jpg", this::loadImage), + new MenuItem("Enregistrer sous...","/dd_27_p3.jpg", e -> {}), + new MenuItem("Quitter","/cp_59_p3.jpg", this::quit) + ); + + drawingMenu = new Menu("Editer","/dd_28_p1.jpg", + new Menu("Dessiner", "/display_14_p3.jpg", + new MenuItem("Couleur", this::chooseColor), + new Menu("Formes", + new MenuItem("Pixel", e -> this.setMode(e, Mode.DRAW_PIXEL)), + new MenuItem("Ligne", e -> this.setMode(e, Mode.DRAW_LINE)), + new MenuItem("Rectangle",e -> this.setMode(e, Mode.DRAW_RECTANGLE)), + new MenuItem("Cercle", e -> this.setMode(e, Mode.DRAW_CIRCLE)) + ) + ), + new Menu("Convertir", + new MenuItem("RGB", e -> {}), + new MenuItem("NG", e -> {}) + ) + ); + + fourierMenu = new Menu("Fourier","/cp_51_p1.jpg", + new MenuItem("Afficher", "/cp_51_p3.jpg", this::displayFourier) + ); + + linearMenu = new Menu("Linéaire","/cp_51_p1.jpg", + new Menu("Global", + new Menu("Formes", + new MenuItem("PasseBasIdeal", e -> this.setMode(e, Mode.DRAW_PIXEL)), + new MenuItem("PasseHautIdeal", e -> this.setMode(e, Mode.DRAW_LINE)), + new MenuItem("PasseBasButterworth",e -> this.setMode(e, Mode.DRAW_RECTANGLE)), + new MenuItem("PasseHautButterworth", e -> this.setMode(e, Mode.DRAW_CIRCLE)) + ) + ) + ); + + histogramMenu = new Menu("Histogramme","/report_48_hot.jpg", + new MenuItem("Afficher", "/report_32_hot.jpg",this::displayHistogram) + ); + + this.add(imageMenu); + this.add(drawingMenu); + this.add(fourierMenu); + this.add(linearMenu); + this.add(histogramMenu); + + drawingMenu.setEnabled(false); + fourierMenu.setEnabled(false); + linearMenu.setEnabled(false); + histogramMenu.setEnabled(false); + } + + //#################################################### + + private File chooseFile(){ + FileChooser chooser = new FileChooser("./"); + + int dialogResult = chooser.showOpenDialog(this); + if (dialogResult != JFileChooser.APPROVE_OPTION) return null; + + return chooser.getSelectedFile(); + } + + private void loadImage(ActionEvent e) { + try + { + File file = chooseFile(); + if(file == null) return; + presenter.loadImage(file); + activeMenusRGB(); + activeMenusNG(); + } + catch (IOException ex) + { + System.err.println("Erreur I/O : " + ex.getMessage()); + } + } + + private void quit(ActionEvent e) { + System.exit(0); + } + + private void createImage(ActionEvent e, ImageCreatorDialog dialog) { + dialog.setVisible(true); + presenter.createImage( + dialog.getRed(), dialog.getGreen(), dialog.getBlue(), + dialog.getImageHeight(), dialog.getImageWidth()); + activeMenusRGB(); + activeMenusNG(); + } + + private void setMode(ActionEvent e, Mode mode) { + presenter.setMode(mode); + } + + private void chooseColor(ActionEvent e) { + /*if (imageRGB != null) + { + Color newC = JColorChooser.showDialog(this,"Couleur du pinceau", couleurPinceauRGB); + if (newC != null) couleurPinceauRGB = newC; + observer.setCouleurPinceau(couleurPinceauRGB); + } + + if (imageNG != null) + { + GreyScalePicker dialog = new GreyScalePicker(new JFrame(),true, couleurPinceauNG); + dialog.setVisible(true); + couleurPinceauNG = dialog.getCouleur(); + }*/ + } + + + private void activeMenusNG() + { + drawingMenu.setEnabled(true); + fourierMenu.setEnabled(true); + linearMenu.setEnabled(true); + histogramMenu.setEnabled(true); + } + + private void activeMenusRGB() + { + drawingMenu.setEnabled(true); + fourierMenu.setEnabled(false); + linearMenu.setEnabled(false); + histogramMenu.setEnabled(false); + } + + //#################################################### + + private void displayHistogram(ActionEvent e) { + GrayScaleMatrix i_graymatrix = presenter.getImageGrayMatrix(); + int[] histo = Histogramme.Histogramme256(i_graymatrix); + + //Création du dataset + XYSeries serie = new XYSeries("Histo"); + for(int i=0 ; i<256 ; i++) serie.add(i,histo[i]); + XYSeriesCollection dataset = new XYSeriesCollection(); + dataset.addSeries(serie); + + // Creation du chart + JFreeChart chart = ChartFactory.createHistogram("Histogramme","Niveaux de gris", + "Nombre de pixels", dataset, PlotOrientation.VERTICAL,false,false,false); + + XYPlot plot = chart.getXYPlot(); + ValueAxis axeX = plot.getDomainAxis(); + axeX.setRange(0,255); + plot.setDomainAxis(axeX); + + // creation d'une frame + ChartFrame frame = new ChartFrame("Histogramme de l'image",chart); + frame.pack(); + frame.setVisible(true); + } + + private void jMenuItemFourierAfficherPartieImaginaireActionPerformed(ActionEvent e) { +// try +// { +// int f_int[][] = imageNG.getMatrice(); +// double f[][] = new double[imageNG.getLargeur()][imageNG.getHauteur()]; +// for(int i=0 ; i//GEN-BEGIN:initComponents + private void initComponents() { + jSlider = new javax.swing.JSlider(); + jPanel = new javax.swing.JPanel(); + jButtonOk = new javax.swing.JButton(); + + setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE); + setTitle("Choix du niveau de gris"); + setResizable(false); + jSlider.setMajorTickSpacing(255); + jSlider.setMaximum(255); + jSlider.setMinorTickSpacing(15); + jSlider.setPaintLabels(true); + jSlider.setPaintTicks(true); + jSlider.addChangeListener(new javax.swing.event.ChangeListener() { + public void stateChanged(javax.swing.event.ChangeEvent evt) { + jSliderStateChanged(evt); + } + }); + + jPanel.setBorder(javax.swing.BorderFactory.createCompoundBorder(null, javax.swing.BorderFactory.createLineBorder(new Color(255, 0, 0)))); + javax.swing.GroupLayout jPanelLayout = new javax.swing.GroupLayout(jPanel); + jPanel.setLayout(jPanelLayout); + jPanelLayout.setHorizontalGroup( + jPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGap(0, 62, Short.MAX_VALUE) + ); + jPanelLayout.setVerticalGroup( + jPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGap(0, 45, Short.MAX_VALUE) + ); + + jButtonOk.setText("Ok"); + jButtonOk.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + jButtonOkActionPerformed(evt); + } + }); + + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane()); + getContentPane().setLayout(layout); + layout.setHorizontalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addContainerGap() + .addComponent(jSlider, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGap(19, 19, 19) + .addComponent(jPanel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 20, Short.MAX_VALUE) + .addComponent(jButtonOk, javax.swing.GroupLayout.PREFERRED_SIZE, 65, javax.swing.GroupLayout.PREFERRED_SIZE) + .addContainerGap()) + ); + layout.setVerticalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() + .addContainerGap() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING) + .addComponent(jButtonOk, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.DEFAULT_SIZE, 47, Short.MAX_VALUE) + .addComponent(jPanel, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(jSlider, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + .addContainerGap()) + ); + pack(); + }// //GEN-END:initComponents + + private void jButtonOkActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jButtonOkActionPerformed + couleur = jSlider.getValue(); + setVisible(false); + dispose(); + }//GEN-LAST:event_jButtonOkActionPerformed + + private void jSliderStateChanged(javax.swing.event.ChangeEvent evt) {//GEN-FIRST:event_jSliderStateChanged + int valeur = jSlider.getValue(); + Color c = new Color(valeur,valeur,valeur); + jPanel.setBackground(c); + }//GEN-LAST:event_jSliderStateChanged + + public int getCouleur() { return couleur; } + + /** + * @param args the command line arguments + */ + public static void main(String args[]) { + java.awt.EventQueue.invokeLater(new Runnable() { + public void run() { + new GreyScalePicker(new javax.swing.JFrame(), true, 128).setVisible(true); + } + }); + } + + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JButton jButtonOk; + private javax.swing.JPanel jPanel; + private javax.swing.JSlider jSlider; + // End of variables declaration//GEN-END:variables + +} diff --git a/src/main/java/ui/implementation/dialogs/ImageCreatorDialog.java b/src/main/java/ui/implementation/dialogs/ImageCreatorDialog.java new file mode 100644 index 0000000..e12a37d --- /dev/null +++ b/src/main/java/ui/implementation/dialogs/ImageCreatorDialog.java @@ -0,0 +1,167 @@ +package ui.implementation.dialogs; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.ActionEvent; + +public abstract class ImageCreatorDialog extends JDialog +{ + protected Color color; + protected int height; + protected int width; + + protected JButton confirmButton; + protected JPanel colorVisualizer; + protected JComponent colorPickerContainer; + + protected JTextField heightInput; + protected JTextField widthInput; + + public ImageCreatorDialog(Frame parent, boolean modal) + { + super(parent, modal); + initComponents(); + + color = new Color(255,255,255); + width = 256; + height = 256; + } + + private void initComponents() { + colorVisualizer = new JPanel(); + confirmButton = new JButton(); + widthInput = new JTextField(); + heightInput = new JTextField(); + + setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); + setTitle("Choix de la couleur"); + setResizable(false); + + GroupLayout jPanelLayout = new GroupLayout(colorVisualizer); + colorVisualizer.setBorder( + BorderFactory.createCompoundBorder(null, + BorderFactory.createLineBorder(new Color(255, 0, 0))) + ); + colorVisualizer.setLayout(jPanelLayout); + + jPanelLayout.setHorizontalGroup( + jPanelLayout.createParallelGroup(GroupLayout.Alignment.LEADING) + .addGap(0, 62, Short.MAX_VALUE) + ); + jPanelLayout.setVerticalGroup( + jPanelLayout.createParallelGroup(GroupLayout.Alignment.LEADING) + .addGap(0, 55, Short.MAX_VALUE) + ); + + confirmButton.setText("Ok"); + confirmButton.addActionListener(this::onConfirmClicked); + + widthInput.setHorizontalAlignment(JTextField.CENTER); + + heightInput.setHorizontalAlignment(JTextField.CENTER); + + JLabel widthLabel = new JLabel("Largeur"); + JLabel heightLabel = new JLabel("Hauteur"); + + colorPickerContainer = new JPanel(new BorderLayout()); + + GroupLayout layout = new GroupLayout(getContentPane()); + getContentPane().setLayout(layout); + layout.setHorizontalGroup( + layout.createParallelGroup(GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addGap(27, 27, 27) + .addGroup(layout.createParallelGroup(GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addComponent(widthLabel) + .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) + .addComponent(widthInput, GroupLayout.PREFERRED_SIZE, 97, GroupLayout.PREFERRED_SIZE) + .addGap(37, 37, 37) + .addComponent(heightLabel) + .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) + .addComponent(heightInput, GroupLayout.PREFERRED_SIZE, 96, GroupLayout.PREFERRED_SIZE) + .addContainerGap(51, Short.MAX_VALUE)) + .addGroup(layout.createSequentialGroup() + .addComponent(colorVisualizer, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE) + .addGap(25, 25, 25) + .addComponent(colorPickerContainer) + .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED, 135, Short.MAX_VALUE) + .addComponent(confirmButton, GroupLayout.PREFERRED_SIZE, 65, GroupLayout.PREFERRED_SIZE) + .addGap(26, 26, 26)))) + ); + + layout.linkSize(SwingConstants.HORIZONTAL, heightInput, widthInput); + + layout.setVerticalGroup( + layout.createParallelGroup(GroupLayout.Alignment.LEADING) + .addGroup(GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() + .addGap(24, 24, 24) + .addGroup(layout.createParallelGroup(GroupLayout.Alignment.BASELINE) + .addComponent(heightInput, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE) + .addComponent(heightLabel) + .addComponent(widthInput, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE) + .addComponent(widthLabel) + ) + .addGap(37, 37, 37) + .addGroup(layout.createParallelGroup(GroupLayout.Alignment.LEADING) + .addGroup(layout.createParallelGroup(GroupLayout.Alignment.BASELINE) + .addComponent(confirmButton, GroupLayout.DEFAULT_SIZE, 57, Short.MAX_VALUE) + .addComponent(colorPickerContainer, GroupLayout.DEFAULT_SIZE, 57, Short.MAX_VALUE)) + .addComponent(colorVisualizer, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + .addContainerGap()) + ); + pack(); + colorVisualizer.setBackground(color); + widthInput.setText("256"); + heightInput.setText("256"); + } + + protected void onColorChosen(ActionEvent e) { + Color color = JColorChooser.showDialog(this,"Couleur de fond", this.color); + if (color == null) return; + this.color = color; + colorVisualizer.setBackground(this.color); + } + + protected void onConfirmClicked(ActionEvent e) { + try + { + height = Integer.parseInt(widthInput.getText()); + width = Integer.parseInt(heightInput.getText()); + + setVisible(false); + dispose(); + } catch(NumberFormatException nfe) + { + JOptionPane.showMessageDialog(this, + "Hauteur et Largeur doivent etre entiers !","Erreur", + JOptionPane.ERROR_MESSAGE); + } + } + + public Color getImageColor() { + return color; + } + + public int getRed() { + return color.getRed(); + } + + public int getGreen() { + return color.getGreen(); + } + + public int getBlue() { + return color.getBlue(); + } + + public int getImageWidth(){ + return width; + } + + public int getImageHeight(){ + return height; + } + + protected abstract void createColorPicker(); +} diff --git a/src/main/java/ui/implementation/dialogs/RGBImageCreatorDialog.java b/src/main/java/ui/implementation/dialogs/RGBImageCreatorDialog.java new file mode 100644 index 0000000..8adca0e --- /dev/null +++ b/src/main/java/ui/implementation/dialogs/RGBImageCreatorDialog.java @@ -0,0 +1,24 @@ +package ui.implementation.dialogs; + +import javax.swing.*; +import java.awt.*; + +public class RGBImageCreatorDialog extends ImageCreatorDialog +{ + + private JButton colorPickerButton; + + public RGBImageCreatorDialog(Frame parent, boolean modal) + { + super(parent, modal); + createColorPicker(); + } + + @Override + protected void createColorPicker() { + colorPickerButton = new JButton(); + colorPickerButton.setText("Choisir"); + colorPickerButton.addActionListener(this::onColorChosen); + colorPickerContainer.add(colorPickerButton); + } +} diff --git a/src/main/java/ui/implementation/views/DoubleMatrix.java b/src/main/java/ui/implementation/views/DoubleMatrix.java new file mode 100644 index 0000000..f7cd691 --- /dev/null +++ b/src/main/java/ui/implementation/views/DoubleMatrix.java @@ -0,0 +1,374 @@ +package ui.implementation.views; + +import domain.image.GrayScaleMatrix; +import domain.image.Image; +import jakarta.inject.Inject; +import presenters.DoubleMatrixPresenter; +import ui.implementation.components.image.ImagePanel; +import ui.implementation.components.intent.FileChooser; +import ui.interfaces.IDoubleMatrix; + +import java.awt.*; +import java.awt.event.ActionEvent; +import java.io.*; +import java.util.Objects; + +import javax.swing.*; +import javax.swing.event.ChangeEvent; + +/** + * @author Jean-Marc Wagner, Laurent Crema + */ +public class DoubleMatrix extends JFrame implements IDoubleMatrix +{ + private double normalizedWhite; + private double normalizedBlack; + + private boolean isUpdating = false; + + private JTabbedPane modeTab; + private ImagePanel modulePreviewContainer; + private ImagePanel phasePreviewContainer; + private ImagePanel realPreviewContainer; + private ImagePanel imaginaryPreviewContainer; + + private JSlider whiteSlider; + private JTextField whiteTextField; + private JLabel maxLabel; + + private JSlider blackSlider; + private JTextField blackTextField; + private JLabel minLabel; + + private final DoubleMatrixPresenter presenter; + + @Inject + public DoubleMatrix(DoubleMatrixPresenter presenter) + { + this.presenter = presenter; + initComponents(); + presenter.setView(this); + initValues(); + } + + private void initComponents() { + + modulePreviewContainer = new ImagePanel(); + phasePreviewContainer = new ImagePanel(); + realPreviewContainer = new ImagePanel(); + imaginaryPreviewContainer = new ImagePanel(); + + blackSlider = buildSlider(); + whiteSlider = buildSlider(); + + blackTextField = buildTextField(Color.BLACK); + whiteTextField = buildTextField(null); + + minLabel = buildValueLabel(); + maxLabel = buildValueLabel(); + JLabel minValueText = new JLabel("Valeur MIN :"); + JLabel maxValueText = new JLabel("Valeur MAX :"); + + JButton saveButton = new JButton(new ImageIcon(Objects.requireNonNull(getClass().getResource("/dd_27_p3.jpg")))); + saveButton.addActionListener(this::saveImage); + + blackSlider.addChangeListener(this::onValueUpdate); + whiteSlider.addChangeListener(this::onValueUpdate); + + blackTextField.addActionListener(this::jTextFieldNoirActionPerformed); + whiteTextField.addActionListener(this::jTextFieldBlancActionPerformed); + + modeTab = buildModeTab(); + buildLayout(modeTab, maxValueText, minValueText, minLabel, maxLabel, saveButton); + + setTitle("Fourier"); + setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); + pack(); + } + + private void initValues(){ + modeTab.setSelectedIndex(1); + + blackSlider.setValue(0); + whiteSlider.setValue(1000); + } + + private JSlider buildSlider() { + return new JSlider(JSlider.VERTICAL); + } + + private JTextField buildTextField(Color background) { + JTextField field = new JTextField(); + field.setFont(new Font("Tahoma", Font.BOLD, 11)); + field.setForeground(Color.RED); + field.setHorizontalAlignment(JTextField.CENTER); + if (background != null) field.setBackground(background); + return field; + } + + private JLabel buildValueLabel() { + JLabel label = new JLabel("0.0"); + label.setFont(new Font("Tahoma", Font.BOLD, 11)); + label.setForeground(Color.BLUE); + return label; + } + + private JTabbedPane buildModeTab(){ + + JTabbedPane modeTabs = new JTabbedPane(); + modeTabs.addTab("Module", modulePreviewContainer); + modeTabs.addTab("Phase", phasePreviewContainer); + modeTabs.addTab("Partie réelle", realPreviewContainer); + modeTabs.addTab("Partie imaginaire", imaginaryPreviewContainer); + modeTabs.addChangeListener(e -> { + switch (modeTabs.getSelectedIndex()) { + case 0 -> presenter.loadModule(); + case 1 -> presenter.loadPhase(); + case 2 -> presenter.loadReal(); + case 3 -> presenter.loadImaginary(); + } + }); + return modeTabs; + } + + private void buildLayout(JTabbedPane modeTab, + JLabel labelMax, JLabel labelMin, + JLabel valeurMin, JLabel valeurMax, + JButton saveButton) { + + JPanel rightPanel = new JPanel(); + GroupLayout rightLayout = new GroupLayout(rightPanel); + rightPanel.setLayout(rightLayout); + + rightLayout.setHorizontalGroup( + rightLayout.createParallelGroup(GroupLayout.Alignment.LEADING) + .addComponent(whiteTextField, GroupLayout.PREFERRED_SIZE, 90, GroupLayout.PREFERRED_SIZE) + .addGroup(rightLayout.createSequentialGroup() + .addComponent(blackSlider) + .addPreferredGap(LayoutStyle.ComponentPlacement.UNRELATED) + .addComponent(whiteSlider) + ) + .addComponent(blackTextField, GroupLayout.PREFERRED_SIZE, 90, GroupLayout.PREFERRED_SIZE) + ); + + rightLayout.setVerticalGroup( + rightLayout.createSequentialGroup() + .addComponent(whiteTextField, GroupLayout.PREFERRED_SIZE, + GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE) + .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) + .addGroup(rightLayout.createParallelGroup(GroupLayout.Alignment.CENTER) + .addComponent(blackSlider) + .addComponent(whiteSlider) + ) + .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) + .addComponent(blackTextField, GroupLayout.PREFERRED_SIZE, + GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE) + ); + + // ------------------------- + // MAIN LAYOUT + // ------------------------- + GroupLayout layout = new GroupLayout(getContentPane()); + getContentPane().setLayout(layout); + + layout.setHorizontalGroup( + layout.createParallelGroup(GroupLayout.Alignment.LEADING) + + // TOP BAR + .addGroup(layout.createSequentialGroup() + .addContainerGap() + .addComponent(labelMin) + .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) + .addComponent(valeurMin) + .addGap(50) + .addComponent(labelMax) + .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) + .addComponent(valeurMax) + .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED, + GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(saveButton) + .addContainerGap() + ) + + // MAIN ROW + .addGroup(layout.createSequentialGroup() + .addContainerGap() + .addComponent(modeTab, 400, 400, Short.MAX_VALUE) + .addPreferredGap(LayoutStyle.ComponentPlacement.UNRELATED) + .addComponent(rightPanel, GroupLayout.PREFERRED_SIZE, + GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE) + .addContainerGap() + ) + ); + + layout.setVerticalGroup( + layout.createSequentialGroup() + + // TOP BAR + .addGroup(layout.createParallelGroup(GroupLayout.Alignment.BASELINE) + .addComponent(labelMin) + .addComponent(valeurMin) + .addComponent(labelMax) + .addComponent(valeurMax) + .addComponent(saveButton) + ) + + .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) + + // MAIN CONTENT + .addGroup(layout.createParallelGroup(GroupLayout.Alignment.LEADING) + .addComponent(modeTab) + .addComponent(rightPanel) + ) + ); + } + + private File chooseFile(){ + FileChooser chooser = new FileChooser("./"); + + int dialogResult = chooser.showOpenDialog(this); + if (dialogResult != JFileChooser.APPROVE_OPTION) return null; + + return chooser.getSelectedFile(); + } + + private void onValueUpdate(ChangeEvent e) { + if (isUpdating) return; + isUpdating = true; + + try { + int black = blackSlider.getValue(); + int white = whiteSlider.getValue(); + + int minGap = 1; + if (white - black < minGap) { + if (blackSlider.getValueIsAdjusting()) { + black = white - minGap; + blackSlider.setValue(black); + } else { + white = black + minGap; + whiteSlider.setValue(white); + } + } + + normalizedBlack = (double) black / blackSlider.getMaximum(); + normalizedWhite = (double) white / whiteSlider.getMaximum(); + + switch (modeTab.getSelectedIndex()) { + case 0 -> presenter.clipModule(normalizedBlack, normalizedWhite); + case 1 -> presenter.clipPhase(normalizedBlack, normalizedWhite); + case 2 -> presenter.clipReal(normalizedBlack, normalizedWhite); + case 3 -> presenter.clipImaginary(normalizedBlack, normalizedWhite); + } + + } finally { + isUpdating = false; + } + } + + + @Override + public void updateModule(GrayScaleMatrix source, GrayScaleMatrix clipped) { + updatePreview(modulePreviewContainer, source, clipped); + } + + @Override + public void updatePhase(GrayScaleMatrix source, GrayScaleMatrix clipped) { + updatePreview(phasePreviewContainer, source, clipped); + } + + @Override + public void updateReal(GrayScaleMatrix source, GrayScaleMatrix clipped) { + updatePreview(realPreviewContainer, source, clipped); + } + + @Override + public void updateImaginary(GrayScaleMatrix source, GrayScaleMatrix clipped) { + updatePreview(imaginaryPreviewContainer, source, clipped); + } + + private void updatePreview(ImagePanel imagePanel, GrayScaleMatrix source, GrayScaleMatrix matrix) { + try { + imagePanel.loadImage(matrix); + updateLabels(source.getMinValue(), source.getMaxValue()); + updateTextFields( + presenter.getValueAt(source, normalizedBlack), + presenter.getValueAt(source, normalizedWhite) + ); + } catch (Exception e){ + displayErrorMessage("Oups !", e.getMessage()); + } + } + + private void updateTextFields(double black, double white) { + blackTextField.setText(String.format("%.5f", black)); + whiteTextField.setText(String.format("%.5f", white)); + } + + private void updateLabels(double black, double white) { + minLabel.setText(String.format("%.5f", black)); + maxLabel.setText(String.format("%.5f", white)); + } + + private void saveImage(ActionEvent evt) { + + File file = chooseFile(); + if (file != null) { + try { + presenter.saveImage(null); + //image.enregistreFormatPNG(fichier); + } catch (IOException ex) { + System.err.println("Erreur I/O : " + ex.getMessage()); + } + } + } + + private void displayErrorMessage(String title, String message) { + JOptionPane.showMessageDialog( + this, + message, + title, + JOptionPane.ERROR_MESSAGE + ); + } + + private void jTextFieldNoirActionPerformed(ActionEvent evt) { +// double val = Double.parseDouble(jTextFieldNoir.getText()); +// +// double black = presenter.getBlackLevel(); +// double white = presenter.getWhiteLevel(); +// +// if (val < black) +// { +// jSliderNoir.setValue(0); +// return; +// } +// if (val >= normalizedWhite) +// { +// jSliderNoir.setValue(jSliderBlanc.getValue()-1); +// return; +// } +// int s = (int)((double)D*(val-black)/(white-black)+0.5); +// jSliderNoir.setValue(s); + + } + + private void jTextFieldBlancActionPerformed(ActionEvent evt) { +// double val = Double.parseDouble(jTextFieldBlanc.getText()); +// double black = presenter.getBlackLevel(); +// double white = presenter.getWhiteLevel(); +// +// if (val > white) +// { +// jSliderBlanc.setValue(D); +// return; +// } +// if (val <= normalizedBlack) +// { +// jSliderBlanc.setValue(jSliderNoir.getValue()+1); +// return; +// } +// int s = (int)((double)D*(val-black)/(white-black)+0.5); +// jSliderBlanc.setValue(s); + } +} diff --git a/src/main/java/ui/implementation/views/MainView.java b/src/main/java/ui/implementation/views/MainView.java new file mode 100644 index 0000000..03afaa0 --- /dev/null +++ b/src/main/java/ui/implementation/views/MainView.java @@ -0,0 +1,201 @@ +package ui.implementation.views; + +import com.google.inject.Inject; +import domain.common.Mode; +import domain.image.Image; +import infrastructure.ui.ImageMapper; +import presenters.MainPresenter; +import ui.implementation.components.image.ImagePanel; +import ui.implementation.components.nav.NavBar; +import ui.interfaces.IMainView; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.awt.event.MouseMotionAdapter; +import java.awt.image.BufferedImage; + +/** + * @author Jean-Marc Wagner, Laurent Crema + */ +public class MainView extends JFrame implements IMainView +{ + private Mode currentMode; + private ImagePanel imagePreviewContainer; + private final MainPresenter presenter; + + @Inject + public MainView(MainPresenter presenter){ + this.presenter = presenter; + this.presenter.setView(this); + initComponents(); + } + + public void setNavBar(NavBar navBar) { + setJMenuBar(navBar); + } + + private void initComponents() { + imagePreviewContainer = new ImagePanel(); + + imagePreviewContainer.addMouseMotionListener(new MouseMotionAdapter() { + @Override + public void mouseDragged(MouseEvent e) { + drawPixel(e.getX(), e.getY(), 255, 0, 0); + } + }); + + imagePreviewContainer.addMouseListener(new MouseAdapter() { + + private Point startPoint; + + @Override + public void mousePressed(MouseEvent e) { + startPoint = new Point(e.getX(), e.getY()); + } + + @Override + public void mouseReleased(MouseEvent e) { + Point endPoint = new Point(e.getX(), e.getY()); + if (startPoint == null) return; + + drawShape( + startPoint.x, + endPoint.x, + startPoint.y, + endPoint.y, + 255, 0, 0 + ); + + startPoint = null; + } + }); + + GroupLayout layout = new GroupLayout(getContentPane()); + getContentPane().setLayout(layout); + layout.setHorizontalGroup( + layout.createParallelGroup(GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addContainerGap() + .addComponent(imagePreviewContainer) + .addContainerGap()) + ); + layout.setVerticalGroup( + layout.createParallelGroup(GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addContainerGap() + .addComponent(imagePreviewContainer) + .addContainerGap()) + ); + + setSize(new Dimension(500, 400)); + setLocationRelativeTo(null); + } + + public void drawPixel(int x, int y, int red, int green, int blue){ + try { + Point p = imagePreviewContainer.toImageCoordinates(x, y); + if(p == null) return; + presenter.drawPixel((int)p.getX(), (int)p.getY(), red, green, blue); + } catch (Exception e) { + displayErrorMessage("Oups !", + "Impossible de dessiner sur l'image pour le moment : " + e.getMessage()); + } + } + + public void drawShape(int x1, int x2, int y1, int y2, int red, int green, int blue){ + try { + Point p1 = imagePreviewContainer.toImageCoordinates(x1, y1); + Point p2 = imagePreviewContainer.toImageCoordinates(x2, y2); + if(p1 == null || p2 == null) return; + presenter.drawShape( + (int)p1.getX(), (int)p2.getX(), (int) p1.getY(), (int) p2.getY(), + red, green, blue); + + } catch (Exception e) { + displayErrorMessage("Oups !", + "Impossible de dessiner sur l'image pour le moment : " + e.getMessage()); + } + } + + +// public void SelectCercleDetected(DeuxClicsEvent e) +// { +// if (presenter.isCircleModeActive()) +// { +// try +// { +// if (imageRGB != null) +// imageRGB.DessineCercle(e.getX1(),e.getY1(),e.getX2(),e.getY2(),couleurPinceauRGB); +// if (imageNG != null) +// imageNG.DessineCercle(e.getX1(),e.getY1(),e.getX2(),e.getY2(),couleurPinceauNG); +// } +// catch (CImageRGBException ex) +// { System.out.println("Erreur RGB : " + ex.getMessage()); } +// catch (CImageNGException ex) +// { System.out.println("Erreur NG : " + ex.getMessage()); } +// } +// } +// +// public void SelectCercleFillDetected(DeuxClicsEvent e) +// { +// if (presenter.isPlainCircleModeActive()) +// { +// try +// { +// if (imageRGB != null) +// imageRGB.RemplitCercle(e.getX1(),e.getY1(),e.getX2(),e.getY2(),couleurPinceauRGB); +// if (imageNG != null) +// imageNG.RemplitCercle(e.getX1(),e.getY1(),e.getX2(),e.getY2(),couleurPinceauNG); +// } +// catch (CImageRGBException ex) +// { System.out.println("Erreur RGB : " + ex.getMessage()); } +// catch (CImageNGException ex) +// { System.out.println("Erreur NG : " + ex.getMessage()); } +// } +// } +// +// public void SelectRectFillDetected(DeuxClicsEvent e) +// { +// if (presenter.isPlainRectangleActive()) +// { +// try +// { +// if (imageRGB != null) +// imageRGB.RemplitRect(e.getX1(),e.getY1(),e.getX2(),e.getY2(),couleurPinceauRGB); +// if (imageNG != null) +// imageNG.RemplitRect(e.getX1(),e.getY1(),e.getX2(),e.getY2(),couleurPinceauNG); +// } +// catch (CImageRGBException ex) +// { System.out.println("Erreur RGB : " + ex.getMessage()); } +// catch (CImageNGException ex) +// { System.out.println("Erreur NG : " + ex.getMessage()); } +// } +// } + + @Override + public void displayImage(Image image) { + try { + BufferedImage bufferedImage = ImageMapper.toBufferedImage(image); + imagePreviewContainer.loadImage(bufferedImage); + } catch (Exception ex) { + displayErrorMessage("Oups !", ex.getMessage()); + } + } + + @Override + public void changeMode(Mode m) { + + } + + private void displayErrorMessage(String title, String message) { + JOptionPane.showMessageDialog( + this, + message, + title, + JOptionPane.ERROR_MESSAGE + ); + } + +} diff --git a/src/main/java/ui/interfaces/IDoubleMatrix.java b/src/main/java/ui/interfaces/IDoubleMatrix.java new file mode 100644 index 0000000..42ec9c3 --- /dev/null +++ b/src/main/java/ui/interfaces/IDoubleMatrix.java @@ -0,0 +1,11 @@ +package ui.interfaces; + +import domain.image.GrayScaleMatrix; + +public interface IDoubleMatrix { + + void updateModule(GrayScaleMatrix source, GrayScaleMatrix clipped); + void updatePhase(GrayScaleMatrix source, GrayScaleMatrix clipped); + void updateReal(GrayScaleMatrix source, GrayScaleMatrix clipped); + void updateImaginary(GrayScaleMatrix source, GrayScaleMatrix clipped); +} diff --git a/src/main/java/ui/interfaces/IMainView.java b/src/main/java/ui/interfaces/IMainView.java new file mode 100644 index 0000000..54ed61b --- /dev/null +++ b/src/main/java/ui/interfaces/IMainView.java @@ -0,0 +1,15 @@ +package ui.interfaces; + +import domain.common.Mode; +import domain.image.Image; +import presenters.DoubleMatrixPresenter; +import presenters.MainPresenter; + +import java.awt.image.BufferedImage; + +public interface IMainView { + + void changeMode(Mode m); + void displayImage(Image image); + +} diff --git a/src/main/java/ui/interfaces/INavBar.java b/src/main/java/ui/interfaces/INavBar.java new file mode 100644 index 0000000..7f9b597 --- /dev/null +++ b/src/main/java/ui/interfaces/INavBar.java @@ -0,0 +1,8 @@ +package ui.interfaces; + +import presenters.NavPresenter; + +public interface INavBar { + + +} diff --git a/src/main/resources/Thumbs.db b/src/main/resources/Thumbs.db new file mode 100644 index 0000000..79d32e6 Binary files /dev/null and b/src/main/resources/Thumbs.db differ diff --git a/src/main/resources/cp_51_p1.jpg b/src/main/resources/cp_51_p1.jpg new file mode 100644 index 0000000..5cf394a Binary files /dev/null and b/src/main/resources/cp_51_p1.jpg differ diff --git a/src/main/resources/cp_51_p3.jpg b/src/main/resources/cp_51_p3.jpg new file mode 100644 index 0000000..afd1968 Binary files /dev/null and b/src/main/resources/cp_51_p3.jpg differ diff --git a/src/main/resources/cp_59_p3.jpg b/src/main/resources/cp_59_p3.jpg new file mode 100644 index 0000000..a73120a Binary files /dev/null and b/src/main/resources/cp_59_p3.jpg differ diff --git a/src/main/resources/dd_27_p3.jpg b/src/main/resources/dd_27_p3.jpg new file mode 100644 index 0000000..50859c3 Binary files /dev/null and b/src/main/resources/dd_27_p3.jpg differ diff --git a/src/main/resources/dd_28_p1.jpg b/src/main/resources/dd_28_p1.jpg new file mode 100644 index 0000000..4b09e7b Binary files /dev/null and b/src/main/resources/dd_28_p1.jpg differ diff --git a/src/main/resources/display_14_p3.jpg b/src/main/resources/display_14_p3.jpg new file mode 100644 index 0000000..3ffc98d Binary files /dev/null and b/src/main/resources/display_14_p3.jpg differ diff --git a/src/main/resources/file_65_p3.jpg b/src/main/resources/file_65_p3.jpg new file mode 100644 index 0000000..7ed010b Binary files /dev/null and b/src/main/resources/file_65_p3.jpg differ diff --git a/src/main/resources/folder_036_p3.jpg b/src/main/resources/folder_036_p3.jpg new file mode 100644 index 0000000..3498887 Binary files /dev/null and b/src/main/resources/folder_036_p3.jpg differ diff --git a/src/main/resources/net_13_p1.jpg b/src/main/resources/net_13_p1.jpg new file mode 100644 index 0000000..1c7f09c Binary files /dev/null and b/src/main/resources/net_13_p1.jpg differ diff --git a/src/main/resources/report_32_hot.jpg b/src/main/resources/report_32_hot.jpg new file mode 100644 index 0000000..e95b666 Binary files /dev/null and b/src/main/resources/report_32_hot.jpg differ diff --git a/src/main/resources/report_48_hot.jpg b/src/main/resources/report_48_hot.jpg new file mode 100644 index 0000000..019ca82 Binary files /dev/null and b/src/main/resources/report_48_hot.jpg differ