From 11aceb1f67bd7a6010d756f33ff699b441fb3538 Mon Sep 17 00:00:00 2001 From: Laurent Date: Tue, 22 Sep 2026 18:04:45 +0200 Subject: [PATCH] Add convolution and mean filtering --- src/main/java/app/AppModule.java | 17 +- src/main/java/app/INavigator.java | 3 +- src/main/java/app/SwingNavigator.java | 20 +- ...ngState.java => GlobalFilteringState.java} | 12 +- .../java/app/state/LocalFilteringState.java | 45 ++++ .../filtering/LocalLinearFiltering.java | 120 +++++++++ src/main/java/presenters/NavPresenter.java | 5 +- .../GlobalFilteringListener.java} | 4 +- .../GlobalFilteringPresenter.java} | 20 +- .../local/LocalFilteringListener.java | 10 + .../local/LocalFilteringPresenter.java | 66 +++++ ...rvice.java => GlobalFilteringService.java} | 2 +- .../java/services/LocalFilteringService.java | 23 ++ ...ingView.java => IGlobalFilteringView.java} | 2 +- .../ui/abstractions/ILocalFilteringView.java | 10 + .../implementation/components/nav/NavBar.java | 7 +- .../global}/IdealFilteringView.java | 54 ++-- .../global}/NonIdealFilteringView.java | 12 +- .../fitering/local/LocalFilteringView.java | 238 ++++++++++++++++++ 19 files changed, 602 insertions(+), 68 deletions(-) rename src/main/java/app/state/{FilteringState.java => GlobalFilteringState.java} (78%) create mode 100644 src/main/java/app/state/LocalFilteringState.java create mode 100644 src/main/java/domain/image/processing/filtering/LocalLinearFiltering.java rename src/main/java/presenters/filtering/{FilteringListener.java => global/GlobalFilteringListener.java} (72%) rename src/main/java/presenters/filtering/{FilteringPresenter.java => global/GlobalFilteringPresenter.java} (79%) create mode 100644 src/main/java/presenters/filtering/local/LocalFilteringListener.java create mode 100644 src/main/java/presenters/filtering/local/LocalFilteringPresenter.java rename src/main/java/services/{FilteringService.java => GlobalFilteringService.java} (97%) create mode 100644 src/main/java/services/LocalFilteringService.java rename src/main/java/ui/abstractions/{IFilteringView.java => IGlobalFilteringView.java} (86%) create mode 100644 src/main/java/ui/abstractions/ILocalFilteringView.java rename src/main/java/ui/implementation/views/{ => fitering/global}/IdealFilteringView.java (95%) rename src/main/java/ui/implementation/views/{ => fitering/global}/NonIdealFilteringView.java (96%) create mode 100644 src/main/java/ui/implementation/views/fitering/local/LocalFilteringView.java diff --git a/src/main/java/app/AppModule.java b/src/main/java/app/AppModule.java index 7ef01a9..3272329 100644 --- a/src/main/java/app/AppModule.java +++ b/src/main/java/app/AppModule.java @@ -6,18 +6,17 @@ import com.google.common.eventbus.EventBus; import com.google.inject.AbstractModule; import com.google.inject.Scopes; import presenters.doubleMatrix.DoubleMatrixPresenter; -import presenters.filtering.FilteringPresenter; +import presenters.filtering.global.GlobalFilteringPresenter; import presenters.MainPresenter; import presenters.NavPresenter; +import presenters.filtering.local.LocalFilteringPresenter; import services.ImageService; +import ui.abstractions.*; import ui.implementation.components.nav.NavBar; import ui.implementation.views.DoubleMatrix; -import ui.implementation.views.IdealFilteringView; +import ui.implementation.views.fitering.global.IdealFilteringView; import ui.implementation.views.MainView; -import ui.abstractions.IDoubleMatrix; -import ui.abstractions.IFilteringView; -import ui.abstractions.IMainView; -import ui.abstractions.INavBar; +import ui.implementation.views.fitering.local.LocalFilteringView; /** * Gathers some configurations in order to set the dependency injection container up @@ -40,13 +39,15 @@ public class AppModule extends AbstractModule { bind(INavBar.class).to(NavBar.class); bind(IMainView.class).to(MainView.class); bind(IDoubleMatrix.class).to(DoubleMatrix.class); - bind(IFilteringView.class).to(IdealFilteringView.class); + bind(IGlobalFilteringView.class).to(IdealFilteringView.class); + bind(ILocalFilteringView.class).to(LocalFilteringView.class); //--- Presentation layer --- bind(MainPresenter.class); bind(NavPresenter.class); bind(DoubleMatrixPresenter.class); - bind(FilteringPresenter.class); + bind(GlobalFilteringPresenter.class); + bind(LocalFilteringPresenter.class); //--- Service layer layer --- bind(ImageService.class); diff --git a/src/main/java/app/INavigator.java b/src/main/java/app/INavigator.java index 4a32589..2ff04bb 100644 --- a/src/main/java/app/INavigator.java +++ b/src/main/java/app/INavigator.java @@ -10,6 +10,7 @@ public interface INavigator { * Displays Fourier view */ void showFourier(); - void showFiltering(); + void showIdealFiltering(); void showButterworth(); + void showConvolutionFiltering(); } diff --git a/src/main/java/app/SwingNavigator.java b/src/main/java/app/SwingNavigator.java index e49fc1a..7caf564 100644 --- a/src/main/java/app/SwingNavigator.java +++ b/src/main/java/app/SwingNavigator.java @@ -3,9 +3,10 @@ package app; import com.google.inject.Inject; import com.google.inject.Provider; import ui.implementation.views.DoubleMatrix; -import ui.implementation.views.IdealFilteringView; -import ui.implementation.views.NonIdealFilteringView; +import ui.implementation.views.fitering.global.IdealFilteringView; +import ui.implementation.views.fitering.global.NonIdealFilteringView; import ui.implementation.views.ViewInitializer; +import ui.implementation.views.fitering.local.LocalFilteringView; /** * Concrete implement of the INavigator interface. @@ -17,16 +18,19 @@ public class SwingNavigator implements INavigator { private final Provider doubleMatrixProvider; private final Provider filteringProvider; private final Provider nonIdealFilteringProvider; + private final Provider convolutionFilteringProvider; @Inject public SwingNavigator( Provider doubleMatrixProvider, Provider filteringProvider, - Provider nonIdealFilteringProvider + Provider nonIdealFilteringProvider, + Provider convolutionFilterProvider ) { this.doubleMatrixProvider = doubleMatrixProvider; this.filteringProvider = filteringProvider; this.nonIdealFilteringProvider = nonIdealFilteringProvider; + this.convolutionFilteringProvider = convolutionFilterProvider; } @Override @@ -37,7 +41,7 @@ public class SwingNavigator implements INavigator { } @Override - public void showFiltering() { + public void showIdealFiltering() { IdealFilteringView view = filteringProvider.get(); ViewInitializer.Initialize(view); view.setVisible(true); @@ -49,4 +53,12 @@ public class SwingNavigator implements INavigator { ViewInitializer.Initialize(view); view.setVisible(true); } + + @Override + public void showConvolutionFiltering() { + LocalFilteringView view = convolutionFilteringProvider.get(); + ViewInitializer.Initialize(view); + view.setVisible(true); + } + } diff --git a/src/main/java/app/state/FilteringState.java b/src/main/java/app/state/GlobalFilteringState.java similarity index 78% rename from src/main/java/app/state/FilteringState.java rename to src/main/java/app/state/GlobalFilteringState.java index f53bce2..b205c0e 100644 --- a/src/main/java/app/state/FilteringState.java +++ b/src/main/java/app/state/GlobalFilteringState.java @@ -2,27 +2,27 @@ package app.state; import com.google.inject.Inject; import domain.image.Image; -import presenters.filtering.FilteringListener; +import presenters.filtering.global.GlobalFilteringListener; import java.util.ArrayList; import java.util.List; -public class FilteringState extends State { +public class GlobalFilteringState extends State { private Image lowPass; private Image highPass; private Image butterworthLowPass; private Image butterworthHighPass; - private final List listeners = new ArrayList<>(); + private final List listeners = new ArrayList<>(); @Inject - public FilteringState() {} + public GlobalFilteringState() {} - public void subscribe(FilteringListener listener) { + public void subscribe(GlobalFilteringListener listener) { listeners.add(listener); } - public void unsubscribe(FilteringListener listener) { + public void unsubscribe(GlobalFilteringListener listener) { listeners.remove(listener); } diff --git a/src/main/java/app/state/LocalFilteringState.java b/src/main/java/app/state/LocalFilteringState.java new file mode 100644 index 0000000..d9ddc39 --- /dev/null +++ b/src/main/java/app/state/LocalFilteringState.java @@ -0,0 +1,45 @@ +package app.state; + +import com.google.inject.Inject; +import domain.image.Image; +import presenters.filtering.global.GlobalFilteringListener; +import presenters.filtering.local.LocalFilteringListener; + +import java.util.ArrayList; +import java.util.List; + +public class LocalFilteringState extends State { + private Image convolution; + private Image mean; + + private final List listeners = new ArrayList<>(); + + @Inject + public LocalFilteringState() {} + + public void subscribe(LocalFilteringListener listener) { + listeners.add(listener); + } + + public void unsubscribe(LocalFilteringListener listener) { + listeners.remove(listener); + } + + public void setConvolution(Image image) { + convolution = image; + listeners.forEach(listener -> listener.onConvolutionChanged(image)); + } + + public void setMean(Image image) { + mean = image; + listeners.forEach(listener -> listener.onMeanChanged(image)); + } + + public Image getConvolution() { + return convolution; + } + + public Image getMean() { + return mean; + } +} \ No newline at end of file diff --git a/src/main/java/domain/image/processing/filtering/LocalLinearFiltering.java b/src/main/java/domain/image/processing/filtering/LocalLinearFiltering.java new file mode 100644 index 0000000..85ac310 --- /dev/null +++ b/src/main/java/domain/image/processing/filtering/LocalLinearFiltering.java @@ -0,0 +1,120 @@ +package domain.image.processing.filtering; + +import domain.image.GrayScaleMatrix; +import org.opencv.core.Core; +import org.opencv.core.CvType; +import org.opencv.core.Mat; +import org.opencv.core.Point; +import org.opencv.imgproc.Imgproc; + +public class LocalLinearFiltering { + + public static GrayScaleMatrix convolutionMask(GrayScaleMatrix matrix, double[][] mask) { + validateMask(mask); + return applyMask(matrix, mask); + } + + public static GrayScaleMatrix meanFilter(GrayScaleMatrix matrix, int maskSize) { + validateMaskSize(maskSize); + + double coefficient = 1.0 / (maskSize * maskSize); + double[][] mask = new double[maskSize][maskSize]; + + for (int y = 0; y < maskSize; y++) { + for (int x = 0; x < maskSize; x++) { + mask[y][x] = coefficient; + } + } + + return applyMask(matrix, mask); + } + + private static GrayScaleMatrix applyMask(GrayScaleMatrix matrix, double[][] mask) { + Mat source = toMat(matrix); + int maskSize = mask.length; + + Mat kernel = new Mat(maskSize, maskSize, CvType.CV_64F); + + for (int y = 0; y < maskSize; y++) { + for (int x = 0; x < maskSize; x++) { + kernel.put(y, x, mask[y][x]); + } + } + + Mat destination = new Mat(); + + Imgproc.filter2D( + source, + destination, + CvType.CV_64F, + kernel, + new Point(-1, -1), + 0, + Core.BORDER_REPLICATE + ); + + GrayScaleMatrix result = fromMat(destination); + + source.release(); + kernel.release(); + destination.release(); + + return result; + } + + private static void validateMask(double[][] mask) { + if (mask == null || mask.length == 0 || mask.length % 2 == 0) { + throw new IllegalArgumentException( + "Le masque doit être une matrice carrée de dimension impaire." + ); + } + + int size = mask.length; + + for (double[] row : mask) { + if (row == null || row.length != size) { + throw new IllegalArgumentException( + "Le masque doit être une matrice carrée." + ); + } + } + } + + private static void validateMaskSize(int maskSize) { + if (maskSize <= 0 || maskSize % 2 == 0) { + throw new IllegalArgumentException( + "La taille du masque doit être un entier positif impair." + ); + } + } + + private static Mat toMat(GrayScaleMatrix matrix) { + double[][] image = matrix.getRawData(); + int height = image.length; + int width = image[0].length; + + Mat mat = new Mat(height, width, CvType.CV_64F); + + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + mat.put(y, x, image[y][x]); + } + } + + return mat; + } + + private static GrayScaleMatrix fromMat(Mat mat) { + int height = mat.rows(); + int width = mat.cols(); + double[][] result = new double[height][width]; + + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + result[y][x] = Math.clamp(mat.get(y, x)[0], 0.0, 255.0); + } + } + + return new GrayScaleMatrix(result); + } +} \ No newline at end of file diff --git a/src/main/java/presenters/NavPresenter.java b/src/main/java/presenters/NavPresenter.java index 530ad52..651831d 100644 --- a/src/main/java/presenters/NavPresenter.java +++ b/src/main/java/presenters/NavPresenter.java @@ -47,9 +47,12 @@ public class NavPresenter { } public void goToFiltering() { - navigator.showFiltering(); + navigator.showIdealFiltering(); } + public void goToConvolution() { + navigator.showConvolutionFiltering(); + } public void gotToButterworth() { navigator.showButterworth(); diff --git a/src/main/java/presenters/filtering/FilteringListener.java b/src/main/java/presenters/filtering/global/GlobalFilteringListener.java similarity index 72% rename from src/main/java/presenters/filtering/FilteringListener.java rename to src/main/java/presenters/filtering/global/GlobalFilteringListener.java index 8667b6d..3009c2a 100644 --- a/src/main/java/presenters/filtering/FilteringListener.java +++ b/src/main/java/presenters/filtering/global/GlobalFilteringListener.java @@ -1,9 +1,9 @@ -package presenters.filtering; +package presenters.filtering.global; import domain.events.listeners.Listener; import domain.image.Image; -public interface FilteringListener extends Listener { +public interface GlobalFilteringListener extends Listener { void onLowPassChanged(Image image); void onHighPassChanged(Image image); void onButterworthLowPassChanged(Image image); diff --git a/src/main/java/presenters/filtering/FilteringPresenter.java b/src/main/java/presenters/filtering/global/GlobalFilteringPresenter.java similarity index 79% rename from src/main/java/presenters/filtering/FilteringPresenter.java rename to src/main/java/presenters/filtering/global/GlobalFilteringPresenter.java index 573c435..0f80833 100644 --- a/src/main/java/presenters/filtering/FilteringPresenter.java +++ b/src/main/java/presenters/filtering/global/GlobalFilteringPresenter.java @@ -1,23 +1,23 @@ -package presenters.filtering; +package presenters.filtering.global; import app.state.AppState; -import app.state.FilteringState; +import app.state.GlobalFilteringState; import com.google.inject.Inject; import domain.image.GrayScaleMatrix; import domain.image.Image; -import services.FilteringService; +import services.GlobalFilteringService; import services.ImageService; -import ui.abstractions.IFilteringView; +import ui.abstractions.IGlobalFilteringView; -public class FilteringPresenter implements FilteringListener { +public class GlobalFilteringPresenter implements GlobalFilteringListener { private final AppState appState; - private final FilteringState state; + private final GlobalFilteringState state; private final ImageService imageService; - private final FilteringService filteringService; - private IFilteringView view; + private final GlobalFilteringService filteringService; + private IGlobalFilteringView view; @Inject - public FilteringPresenter(AppState appState, FilteringState state, ImageService imageService, FilteringService filteringService) { + public GlobalFilteringPresenter(AppState appState, GlobalFilteringState state, ImageService imageService, GlobalFilteringService filteringService) { this.appState = appState; this.state = state; this.imageService = imageService; @@ -25,7 +25,7 @@ public class FilteringPresenter implements FilteringListener { this.state.subscribe(this); } - public void setView(IFilteringView view) { + public void setView(IGlobalFilteringView view) { this.view = view; } diff --git a/src/main/java/presenters/filtering/local/LocalFilteringListener.java b/src/main/java/presenters/filtering/local/LocalFilteringListener.java new file mode 100644 index 0000000..9081f02 --- /dev/null +++ b/src/main/java/presenters/filtering/local/LocalFilteringListener.java @@ -0,0 +1,10 @@ +package presenters.filtering.local; + +import domain.image.Image; + +public interface LocalFilteringListener { + + void onConvolutionChanged(Image image); + void onMeanChanged(Image image); + +} diff --git a/src/main/java/presenters/filtering/local/LocalFilteringPresenter.java b/src/main/java/presenters/filtering/local/LocalFilteringPresenter.java new file mode 100644 index 0000000..8223a3c --- /dev/null +++ b/src/main/java/presenters/filtering/local/LocalFilteringPresenter.java @@ -0,0 +1,66 @@ +package presenters.filtering.local; + +import app.state.AppState; +import app.state.LocalFilteringState; +import domain.image.Image; +import jakarta.inject.Inject; +import services.ImageService; +import services.LocalFilteringService; +import ui.abstractions.ILocalFilteringView; + +public class LocalFilteringPresenter implements LocalFilteringListener { + + private final AppState appState; + private final LocalFilteringState state; + private final ImageService imageService; + private final LocalFilteringService filteringService; + + private ILocalFilteringView view; + + @Inject + public LocalFilteringPresenter( + AppState appState, + LocalFilteringState state, + ImageService imageService, + LocalFilteringService filteringService + ) { + this.appState = appState; + this.state = state; + this.imageService = imageService; + this.filteringService = filteringService; + + this.state.subscribe(this); + } + + public void setView(ILocalFilteringView view) { + this.view = view; + } + + public void computeConvolutionFiltering(double[][] mask) { + Image image = filteringService.applyConvolutionMask(appState.getImage(), mask); + state.setConvolution(image); + } + + public void computeMeanFiltering(int value) { + Image image = filteringService.applyMeanFilter(appState.getImage(),value); + state.setMean(image); + } + + @Override + public void onConvolutionChanged(Image image) { + if (view != null) { + view.updateConvolutionFiltering(image); + } + } + + @Override + public void onMeanChanged(Image image) { + if (view != null) { + view.updateMeanFiltering(image); + } + } + + public void dispose() { + state.unsubscribe(this); + } +} \ No newline at end of file diff --git a/src/main/java/services/FilteringService.java b/src/main/java/services/GlobalFilteringService.java similarity index 97% rename from src/main/java/services/FilteringService.java rename to src/main/java/services/GlobalFilteringService.java index bcaec70..ac04956 100644 --- a/src/main/java/services/FilteringService.java +++ b/src/main/java/services/GlobalFilteringService.java @@ -4,7 +4,7 @@ import domain.image.GrayScaleMatrix; import domain.image.Image; import domain.image.processing.filtering.GlobalLinearFiltering; -public class FilteringService { +public class GlobalFilteringService { public Image lowPassFiltering(GrayScaleMatrix matrix, double percentage) { double cutoff = percentage * getMaxCutoff(matrix.getWidth(), matrix.getHeight()); diff --git a/src/main/java/services/LocalFilteringService.java b/src/main/java/services/LocalFilteringService.java new file mode 100644 index 0000000..267253a --- /dev/null +++ b/src/main/java/services/LocalFilteringService.java @@ -0,0 +1,23 @@ +package services; + +import domain.image.GrayScaleMatrix; +import domain.image.Image; +import domain.image.processing.filtering.LocalLinearFiltering; + +public class LocalFilteringService { + + public Image applyConvolutionMask(Image image, double[][] mask) { + GrayScaleMatrix result = LocalLinearFiltering.convolutionMask(getGrayScale(image), mask); + return result.toImage(); + } + + public Image applyMeanFilter(Image image, int maskSize) { + GrayScaleMatrix result = LocalLinearFiltering.meanFilter(getGrayScale(image), maskSize); + return result.toImage(); + } + + private GrayScaleMatrix getGrayScale(Image image) { + return image.toGrayScale(); + } + +} diff --git a/src/main/java/ui/abstractions/IFilteringView.java b/src/main/java/ui/abstractions/IGlobalFilteringView.java similarity index 86% rename from src/main/java/ui/abstractions/IFilteringView.java rename to src/main/java/ui/abstractions/IGlobalFilteringView.java index 74c2ce1..195d8b9 100644 --- a/src/main/java/ui/abstractions/IFilteringView.java +++ b/src/main/java/ui/abstractions/IGlobalFilteringView.java @@ -2,7 +2,7 @@ package ui.abstractions; import domain.image.Image; -public interface IFilteringView { +public interface IGlobalFilteringView { void updateLowPassFiltering(Image image); void updateHighPassFiltering(Image image); void updateButterworthHighPassFiltering(Image image); diff --git a/src/main/java/ui/abstractions/ILocalFilteringView.java b/src/main/java/ui/abstractions/ILocalFilteringView.java new file mode 100644 index 0000000..da12a06 --- /dev/null +++ b/src/main/java/ui/abstractions/ILocalFilteringView.java @@ -0,0 +1,10 @@ +package ui.abstractions; + +import domain.image.Image; + +public interface ILocalFilteringView { + + void updateConvolutionFiltering(Image image); + + void updateMeanFiltering(Image image); +} diff --git a/src/main/java/ui/implementation/components/nav/NavBar.java b/src/main/java/ui/implementation/components/nav/NavBar.java index 5aeea4a..13fa0e2 100644 --- a/src/main/java/ui/implementation/components/nav/NavBar.java +++ b/src/main/java/ui/implementation/components/nav/NavBar.java @@ -86,7 +86,7 @@ public class NavBar extends JMenuBar implements INavBar { new MenuItem("Butterworth", this::displayButterworth) ), new Menu("Local", - new MenuItem("Masque de convolution", this::displayIdealFilter), + new MenuItem("Masque de convolution", this::displayConvolution), new MenuItem("Filtre moyenneur", this::displayButterworth) ) ); @@ -107,6 +107,7 @@ public class NavBar extends JMenuBar implements INavBar { histogramMenu.setEnabled(false); } + //#################################################### private File chooseFile(){ @@ -229,6 +230,10 @@ public class NavBar extends JMenuBar implements INavBar { presenter.gotToButterworth(); } + private void displayConvolution(ActionEvent actionEvent) { + presenter.goToConvolution(); + } + private void displayErrorMessage(String title, String message) { JOptionPane.showMessageDialog( this, diff --git a/src/main/java/ui/implementation/views/IdealFilteringView.java b/src/main/java/ui/implementation/views/fitering/global/IdealFilteringView.java similarity index 95% rename from src/main/java/ui/implementation/views/IdealFilteringView.java rename to src/main/java/ui/implementation/views/fitering/global/IdealFilteringView.java index b8b50fb..4f3443a 100644 --- a/src/main/java/ui/implementation/views/IdealFilteringView.java +++ b/src/main/java/ui/implementation/views/fitering/global/IdealFilteringView.java @@ -1,18 +1,18 @@ -package ui.implementation.views; +package ui.implementation.views.fitering.global; import com.google.inject.Inject; import domain.image.Image; -import presenters.filtering.FilteringPresenter; +import presenters.filtering.global.GlobalFilteringPresenter; import ui.abstractions.AbstractView; import ui.implementation.components.controls.IntensitySlider; import ui.implementation.components.controls.SaveButton; -import ui.abstractions.IFilteringView; +import ui.abstractions.IGlobalFilteringView; import ui.implementation.components.image.TabbedImagePanel; import javax.swing.*; import javax.swing.event.ChangeEvent; -public class IdealFilteringView extends AbstractView implements IFilteringView { +public class IdealFilteringView extends AbstractView implements IGlobalFilteringView { private final static String LOW_PASS_TAB_TITLE = "Passe-bas"; private final static String HIGH_PASS_TAB_TITLE = "Passe-haut"; @@ -20,35 +20,14 @@ public class IdealFilteringView extends AbstractView implements IFilteringView { private TabbedImagePanel modeTab; private IntensitySlider intensitySlider; private JLabel intensityLabel; - private final FilteringPresenter presenter; + private final GlobalFilteringPresenter presenter; private boolean isUpdating; @Inject - public IdealFilteringView(FilteringPresenter presenter) { + public IdealFilteringView(GlobalFilteringPresenter presenter) { this.presenter = presenter; } - @Override - public void initComponents(){ - this.intensityLabel = new JLabel(); - this.intensitySlider = new IntensitySlider(0, 1000); - this.intensitySlider.setOrientation(JSlider.VERTICAL); - - this.modeTab = new TabbedImagePanel(); - this.modeTab.addPreviewTab(LOW_PASS_TAB_TITLE); - this.modeTab.addPreviewTab(HIGH_PASS_TAB_TITLE); - - JButton saveButton = new SaveButton(); - - saveButton.addActionListener(e -> {}); - - buildLayout(modeTab, intensityLabel, intensitySlider, saveButton); - setTitle("Filtre idéal"); - setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); - pack(); - } - - private void buildLayout(JTabbedPane modeTab, JLabel valueTextField, @@ -165,6 +144,27 @@ public class IdealFilteringView extends AbstractView implements IFilteringView { } + @Override + public void initComponents(){ + this.intensityLabel = new JLabel(); + this.intensitySlider = new IntensitySlider(0, 1000); + this.intensitySlider.setOrientation(JSlider.VERTICAL); + + this.modeTab = new TabbedImagePanel(); + this.modeTab.addPreviewTab(LOW_PASS_TAB_TITLE); + this.modeTab.addPreviewTab(HIGH_PASS_TAB_TITLE); + + JButton saveButton = new SaveButton(); + + saveButton.addActionListener(e -> {}); + + buildLayout(modeTab, intensityLabel, intensitySlider, saveButton); + setTitle("Filtre idéal"); + setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); + pack(); + } + + @Override public void initValues(){ this.presenter.setView(this); diff --git a/src/main/java/ui/implementation/views/NonIdealFilteringView.java b/src/main/java/ui/implementation/views/fitering/global/NonIdealFilteringView.java similarity index 96% rename from src/main/java/ui/implementation/views/NonIdealFilteringView.java rename to src/main/java/ui/implementation/views/fitering/global/NonIdealFilteringView.java index 84503c2..6decb5e 100644 --- a/src/main/java/ui/implementation/views/NonIdealFilteringView.java +++ b/src/main/java/ui/implementation/views/fitering/global/NonIdealFilteringView.java @@ -1,10 +1,10 @@ -package ui.implementation.views; +package ui.implementation.views.fitering.global; import com.google.inject.Inject; import domain.image.Image; -import presenters.filtering.FilteringPresenter; +import presenters.filtering.global.GlobalFilteringPresenter; import ui.abstractions.AbstractView; -import ui.abstractions.IFilteringView; +import ui.abstractions.IGlobalFilteringView; import ui.implementation.components.controls.IntensitySlider; import ui.implementation.components.controls.SaveButton; import ui.implementation.components.image.TabbedImagePanel; @@ -12,7 +12,7 @@ import ui.implementation.components.image.TabbedImagePanel; import javax.swing.*; import javax.swing.event.ChangeEvent; -public class NonIdealFilteringView extends AbstractView implements IFilteringView { +public class NonIdealFilteringView extends AbstractView implements IGlobalFilteringView { private final static String LOW_PASS_TAB_TITLE = "Passe-bas"; private final static String HIGH_PASS_TAB_TITLE = "Passe-haut"; @@ -22,11 +22,11 @@ public class NonIdealFilteringView extends AbstractView implements IFilteringVie private IntensitySlider orderSlider; private JLabel intensityLabel; private JLabel orderLabel; - private final FilteringPresenter presenter; + private final GlobalFilteringPresenter presenter; private boolean isUpdating; @Inject - public NonIdealFilteringView(FilteringPresenter presenter) { + public NonIdealFilteringView(GlobalFilteringPresenter presenter) { this.presenter = presenter; } diff --git a/src/main/java/ui/implementation/views/fitering/local/LocalFilteringView.java b/src/main/java/ui/implementation/views/fitering/local/LocalFilteringView.java new file mode 100644 index 0000000..2531e57 --- /dev/null +++ b/src/main/java/ui/implementation/views/fitering/local/LocalFilteringView.java @@ -0,0 +1,238 @@ +package ui.implementation.views.fitering.local; + +import domain.image.Image; +import jakarta.inject.Inject; +import presenters.filtering.local.LocalFilteringPresenter; +import ui.abstractions.AbstractView; +import ui.abstractions.ILocalFilteringView; +import ui.implementation.components.controls.SaveButton; +import ui.implementation.components.image.TabbedImagePanel; + +import javax.swing.*; +import java.awt.*; + +public class LocalFilteringView extends AbstractView implements ILocalFilteringView { + private static final String CONVOLUTION_TAB_TITLE = "Convolution"; + private static final String MEAN_TAB_TITLE = "Moyenneur"; + + private final LocalFilteringPresenter presenter; + + private TabbedImagePanel modeTab; + + private JTextField convolutionSizeField; + private JPanel convolutionMaskPanel; + private JTextField[][] convolutionMaskFields; + + private JTextField meanSizeField; + + @Inject + public LocalFilteringView(LocalFilteringPresenter presenter) { + this.presenter = presenter; + } + + @Override + public void initComponents() { + modeTab = new TabbedImagePanel(); + modeTab.addPreviewTab(CONVOLUTION_TAB_TITLE); + modeTab.addPreviewTab(MEAN_TAB_TITLE); + + convolutionSizeField = new JTextField("3", 5); + JButton createMaskButton = new JButton("Créer le masque"); + createMaskButton.addActionListener(e -> createConvolutionMask()); + + convolutionMaskPanel = new JPanel(); + + JButton applyConvolutionButton = new JButton("Appliquer convolution"); + applyConvolutionButton.addActionListener(e -> applyConvolution()); + + meanSizeField = new JTextField("3", 5); + + JButton applyMeanButton = new JButton("Appliquer moyenneur"); + applyMeanButton.addActionListener(e -> applyMean()); + + JButton saveButton = new SaveButton(); + + buildLayout( + modeTab, + convolutionSizeField, + createMaskButton, + convolutionMaskPanel, + applyConvolutionButton, + meanSizeField, + applyMeanButton, + saveButton + ); + + setTitle("Filtrage linéaire local"); + setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); + pack(); + } + + @Override + public void initValues() { + presenter.setView(this); + createConvolutionMask(); + } + + private void createConvolutionMask() { + try { + int size = Integer.parseInt(convolutionSizeField.getText()); + + if (size <= 0 || size % 2 == 0) { + return; + } + + convolutionMaskPanel.removeAll(); + convolutionMaskPanel.setLayout(new GridLayout(size, size, 3, 3)); + convolutionMaskFields = new JTextField[size][size]; + + for (int y = 0; y < size; y++) { + for (int x = 0; x < size; x++) { + JTextField field = new JTextField("0"); + field.setHorizontalAlignment(JTextField.CENTER); + convolutionMaskFields[y][x] = field; + convolutionMaskPanel.add(field); + } + } + + convolutionMaskPanel.revalidate(); + convolutionMaskPanel.repaint(); + } catch (NumberFormatException ignored) { + } + } + + private void applyConvolution() { + try { + double[][] mask = new double[convolutionMaskFields.length][convolutionMaskFields.length]; + + for (int y = 0; y < mask.length; y++) { + for (int x = 0; x < mask.length; x++) { + mask[y][x] = Double.parseDouble(convolutionMaskFields[y][x].getText()); + } + } + + presenter.computeConvolutionFiltering(mask); + } catch (NumberFormatException ignored) { + } + } + + private void applyMean() { + try { + int maskSize = Integer.parseInt(meanSizeField.getText()); + + if (maskSize <= 0 || maskSize % 2 == 0) { + return; + } + + presenter.computeMeanFiltering(maskSize); + } catch (NumberFormatException ignored) { + } + } + + public void updateConvolutionFiltering(Image image) { + modeTab.loadImage(CONVOLUTION_TAB_TITLE, image); + } + + public void updateMeanFiltering(Image image) { + modeTab.loadImage(MEAN_TAB_TITLE, image); + } + + @Override + public void disposeCore() { + presenter.setView(null); + presenter.dispose(); + } + + private void buildLayout( + JComponent imagePanel, + JTextField convolutionSizeField, + JButton createMaskButton, + JPanel convolutionMaskPanel, + JButton applyConvolutionButton, + JTextField meanSizeField, + JButton applyMeanButton, + JButton saveButton + ) { + JPanel convolutionPanel = new JPanel(); + GroupLayout convolutionLayout = new GroupLayout(convolutionPanel); + convolutionPanel.setLayout(convolutionLayout); + convolutionLayout.setAutoCreateGaps(true); + convolutionLayout.setAutoCreateContainerGaps(true); + + convolutionLayout.setHorizontalGroup( + convolutionLayout.createParallelGroup(GroupLayout.Alignment.CENTER) + .addComponent(convolutionSizeField, GroupLayout.PREFERRED_SIZE, 60, GroupLayout.PREFERRED_SIZE) + .addComponent(createMaskButton) + .addComponent(convolutionMaskPanel) + .addComponent(applyConvolutionButton) + ); + + convolutionLayout.setVerticalGroup( + convolutionLayout.createSequentialGroup() + .addComponent(convolutionSizeField, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE) + .addComponent(createMaskButton) + .addComponent(convolutionMaskPanel) + .addComponent(applyConvolutionButton) + ); + + JPanel meanPanel = new JPanel(); + GroupLayout meanLayout = new GroupLayout(meanPanel); + meanPanel.setLayout(meanLayout); + meanLayout.setAutoCreateGaps(true); + meanLayout.setAutoCreateContainerGaps(true); + + meanLayout.setHorizontalGroup( + meanLayout.createParallelGroup(GroupLayout.Alignment.CENTER) + .addComponent(meanSizeField, GroupLayout.PREFERRED_SIZE, 60, GroupLayout.PREFERRED_SIZE) + .addComponent(applyMeanButton) + ); + + meanLayout.setVerticalGroup( + meanLayout.createSequentialGroup() + .addComponent(meanSizeField, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE) + .addComponent(applyMeanButton) + ); + + JPanel rightPanel = new JPanel(); + JTabbedPane operationTabs = new JTabbedPane(); + operationTabs.addTab("Convolution", convolutionPanel); + operationTabs.addTab("Moyenneur", meanPanel); + + GroupLayout rightLayout = new GroupLayout(rightPanel); + rightPanel.setLayout(rightLayout); + rightLayout.setAutoCreateGaps(true); + rightLayout.setAutoCreateContainerGaps(true); + + rightLayout.setHorizontalGroup( + rightLayout.createParallelGroup(GroupLayout.Alignment.CENTER) + .addComponent(operationTabs) + .addComponent(saveButton) + ); + + rightLayout.setVerticalGroup( + rightLayout.createSequentialGroup() + .addComponent(operationTabs) + .addComponent(saveButton) + ); + + JPanel mainPanel = new JPanel(); + GroupLayout mainLayout = new GroupLayout(mainPanel); + mainPanel.setLayout(mainLayout); + mainLayout.setAutoCreateGaps(true); + mainLayout.setAutoCreateContainerGaps(true); + + mainLayout.setHorizontalGroup( + mainLayout.createSequentialGroup() + .addComponent(imagePanel) + .addComponent(rightPanel) + ); + + mainLayout.setVerticalGroup( + mainLayout.createParallelGroup(GroupLayout.Alignment.CENTER) + .addComponent(imagePanel) + .addComponent(rightPanel) + ); + + setContentPane(mainPanel); + } +} \ No newline at end of file