From 048ff5c7921c15836b8dc80167b52ec47f2c3d0b Mon Sep 17 00:00:00 2001 From: Laurent Date: Tue, 22 Sep 2026 15:10:24 +0200 Subject: [PATCH] Implement Butterworth filters --- src/main/java/app/INavigator.java | 2 +- src/main/java/app/SwingNavigator.java | 25 ++++-- src/main/java/presenters/NavPresenter.java | 6 ++ .../filtering/FilteringPresenter.java | 8 +- src/main/java/services/FilteringService.java | 19 +++-- .../components/controls/IntensitySlider.java | 6 +- .../implementation/components/nav/NavBar.java | 21 ++--- .../views/IdealFilteringView.java | 8 +- .../views/NonIdealFilteringView.java | 77 ++++++++++++------- 9 files changed, 110 insertions(+), 62 deletions(-) diff --git a/src/main/java/app/INavigator.java b/src/main/java/app/INavigator.java index fbc94b4..4a32589 100644 --- a/src/main/java/app/INavigator.java +++ b/src/main/java/app/INavigator.java @@ -11,5 +11,5 @@ public interface INavigator { */ void showFourier(); void showFiltering(); - + void showButterworth(); } diff --git a/src/main/java/app/SwingNavigator.java b/src/main/java/app/SwingNavigator.java index 75d44f8..e49fc1a 100644 --- a/src/main/java/app/SwingNavigator.java +++ b/src/main/java/app/SwingNavigator.java @@ -4,6 +4,7 @@ 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.ViewInitializer; /** @@ -15,27 +16,37 @@ public class SwingNavigator implements INavigator { private final Provider doubleMatrixProvider; private final Provider filteringProvider; + private final Provider nonIdealFilteringProvider; @Inject public SwingNavigator( Provider doubleMatrixProvider, - Provider filteringProvider + Provider filteringProvider, + Provider nonIdealFilteringProvider ) { this.doubleMatrixProvider = doubleMatrixProvider; this.filteringProvider = filteringProvider; + this.nonIdealFilteringProvider = nonIdealFilteringProvider; } @Override public void showFourier() { - DoubleMatrix doubleMatrix = doubleMatrixProvider.get(); - ViewInitializer.Initialize(doubleMatrix); - doubleMatrix.setVisible(true); + DoubleMatrix view = doubleMatrixProvider.get(); + ViewInitializer.Initialize(view); + view.setVisible(true); } @Override public void showFiltering() { - IdealFilteringView filteringView = filteringProvider.get(); - ViewInitializer.Initialize(filteringView); - filteringView.setVisible(true); + IdealFilteringView view = filteringProvider.get(); + ViewInitializer.Initialize(view); + view.setVisible(true); + } + + @Override + public void showButterworth() { + NonIdealFilteringView view = nonIdealFilteringProvider.get(); + ViewInitializer.Initialize(view); + view.setVisible(true); } } diff --git a/src/main/java/presenters/NavPresenter.java b/src/main/java/presenters/NavPresenter.java index 9966386..530ad52 100644 --- a/src/main/java/presenters/NavPresenter.java +++ b/src/main/java/presenters/NavPresenter.java @@ -50,6 +50,11 @@ public class NavPresenter { navigator.showFiltering(); } + + public void gotToButterworth() { + navigator.showButterworth(); + } + public GrayScaleMatrix getImageGrayMatrix() { return appState.getImage().toGrayScale(); } @@ -57,4 +62,5 @@ public class NavPresenter { public void setMode(Mode mode) { modeService.setMode(mode); } + } \ No newline at end of file diff --git a/src/main/java/presenters/filtering/FilteringPresenter.java b/src/main/java/presenters/filtering/FilteringPresenter.java index 98f192b..573c435 100644 --- a/src/main/java/presenters/filtering/FilteringPresenter.java +++ b/src/main/java/presenters/filtering/FilteringPresenter.java @@ -41,15 +41,15 @@ public class FilteringPresenter implements FilteringListener { state.setHighPass(image); } - public void computeButterworthLowPassFiltering(float percentage) { + public void computeButterworthLowPassFiltering(float percentage, int order) { GrayScaleMatrix matrix = imageService.getGrayScale(appState.getImage()); - Image image = filteringService.butterworthLowPassFiltering(matrix, percentage / 100); + Image image = filteringService.butterworthLowPassFiltering(matrix, percentage / 100, order); state.setButterworthLowPass(image); } - public void computeButterworthHighPassFiltering(float percentage) { + public void computeButterworthHighPassFiltering(float percentage, int order) { GrayScaleMatrix matrix = imageService.getGrayScale(appState.getImage()); - Image image = filteringService.butterworthHighPassFiltering(matrix, percentage / 100); + Image image = filteringService.butterworthHighPassFiltering(matrix, percentage / 100, order); state.setButterworthHighPass(image); } diff --git a/src/main/java/services/FilteringService.java b/src/main/java/services/FilteringService.java index a7e38ea..bcaec70 100644 --- a/src/main/java/services/FilteringService.java +++ b/src/main/java/services/FilteringService.java @@ -18,15 +18,20 @@ public class FilteringService { return filteredMatrix.toImage(); } + public Image butterworthLowPassFiltering(GrayScaleMatrix matrix, float percentage, int order) { + double cutoff = percentage * getMaxCutoff(matrix.getWidth(), matrix.getHeight()); + GrayScaleMatrix filteredMatrix = GlobalLinearFiltering.butterworthLowPassFiltering(matrix, cutoff, order); + return filteredMatrix.toImage(); + } + + public Image butterworthHighPassFiltering(GrayScaleMatrix matrix, float percentage, int order) { + double cutoff = percentage * getMaxCutoff(matrix.getWidth(), matrix.getHeight()); + GrayScaleMatrix filteredMatrix = GlobalLinearFiltering.butterworthHighPassFiltering(matrix, cutoff, order); + return filteredMatrix.toImage(); + } + private double getMaxCutoff(int width, int height) { return Math.sqrt(Math.pow(width / 2.0, 2) + Math.pow(height / 2.0, 2)); } - public Image butterworthLowPassFiltering(GrayScaleMatrix matrix, float v) { - return null; - } - - public Image butterworthHighPassFiltering(GrayScaleMatrix matrix, float v) { - return null; - } } diff --git a/src/main/java/ui/implementation/components/controls/IntensitySlider.java b/src/main/java/ui/implementation/components/controls/IntensitySlider.java index 96275e0..3cc5adb 100644 --- a/src/main/java/ui/implementation/components/controls/IntensitySlider.java +++ b/src/main/java/ui/implementation/components/controls/IntensitySlider.java @@ -5,10 +5,10 @@ import java.awt.event.ActionListener; public class IntensitySlider extends JSlider { - public IntensitySlider() { + public IntensitySlider(int min, int max) { super(); - setMinimum(0); - setMaximum(1000); + setMinimum(min); + setMaximum(max); } public float getPercentage(){ diff --git a/src/main/java/ui/implementation/components/nav/NavBar.java b/src/main/java/ui/implementation/components/nav/NavBar.java index ac35a65..5aeea4a 100644 --- a/src/main/java/ui/implementation/components/nav/NavBar.java +++ b/src/main/java/ui/implementation/components/nav/NavBar.java @@ -73,7 +73,7 @@ public class NavBar extends JMenuBar implements INavBar { new MenuItem("NG", e -> { displayErrorMessage("Oups !", "Not implemented."); }) - ) + ) ); fourierMenu = new Menu("Fourier","/cp_51_p1.jpg", @@ -82,13 +82,12 @@ public class NavBar extends JMenuBar implements INavBar { linearMenu = new Menu("Linéaire","/cp_51_p1.jpg", new Menu("Global", - new Menu("Ideal", - new MenuItem("Passe-bas", this::displayFilter), - new MenuItem("Passe-haut", this::displayFilter)), - new Menu("Butterworth", - new MenuItem("Passe-bas", this::displayFilter), - new MenuItem("Passe-haut", this::displayFilter) - ) + new MenuItem("Ideal", this::displayIdealFilter), + new MenuItem("Butterworth", this::displayButterworth) + ), + new Menu("Local", + new MenuItem("Masque de convolution", this::displayIdealFilter), + new MenuItem("Filtre moyenneur", this::displayButterworth) ) ); @@ -222,10 +221,14 @@ public class NavBar extends JMenuBar implements INavBar { } } - private void displayFilter(ActionEvent e) { + private void displayIdealFilter(ActionEvent e) { presenter.goToFiltering(); } + private void displayButterworth(ActionEvent e) { + presenter.gotToButterworth(); + } + 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/IdealFilteringView.java index c726f50..b8b50fb 100644 --- a/src/main/java/ui/implementation/views/IdealFilteringView.java +++ b/src/main/java/ui/implementation/views/IdealFilteringView.java @@ -14,8 +14,8 @@ import javax.swing.event.ChangeEvent; public class IdealFilteringView extends AbstractView implements IFilteringView { - private final static String LOW_PASS_TAB_TITLE = "Passe-haut"; - private final static String HIGH_PASS_TAB_TITLE = "Passe-bas"; + private final static String LOW_PASS_TAB_TITLE = "Passe-bas"; + private final static String HIGH_PASS_TAB_TITLE = "Passe-haut"; private TabbedImagePanel modeTab; private IntensitySlider intensitySlider; @@ -31,7 +31,7 @@ public class IdealFilteringView extends AbstractView implements IFilteringView { @Override public void initComponents(){ this.intensityLabel = new JLabel(); - this.intensitySlider = new IntensitySlider(); + this.intensitySlider = new IntensitySlider(0, 1000); this.intensitySlider.setOrientation(JSlider.VERTICAL); this.modeTab = new TabbedImagePanel(); @@ -43,7 +43,7 @@ public class IdealFilteringView extends AbstractView implements IFilteringView { saveButton.addActionListener(e -> {}); buildLayout(modeTab, intensityLabel, intensitySlider, saveButton); - setTitle("Filtering"); + setTitle("Filtre idéal"); setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); pack(); } diff --git a/src/main/java/ui/implementation/views/NonIdealFilteringView.java b/src/main/java/ui/implementation/views/NonIdealFilteringView.java index 171007e..84503c2 100644 --- a/src/main/java/ui/implementation/views/NonIdealFilteringView.java +++ b/src/main/java/ui/implementation/views/NonIdealFilteringView.java @@ -14,12 +14,14 @@ import javax.swing.event.ChangeEvent; public class NonIdealFilteringView extends AbstractView implements IFilteringView { - private final static String LOW_PASS_TAB_TITLE = "Passe-haut"; - private final static String HIGH_PASS_TAB_TITLE = "Passe-bas"; + private final static String LOW_PASS_TAB_TITLE = "Passe-bas"; + private final static String HIGH_PASS_TAB_TITLE = "Passe-haut"; private TabbedImagePanel modeTab; private IntensitySlider intensitySlider; + private IntensitySlider orderSlider; private JLabel intensityLabel; + private JLabel orderLabel; private final FilteringPresenter presenter; private boolean isUpdating; @@ -28,35 +30,36 @@ public class NonIdealFilteringView extends AbstractView implements IFilteringVie this.presenter = presenter; } - - - private void buildLayout(JTabbedPane modeTab, - JLabel valueTextField, - JSlider intensitySlider, - JButton saveButton) { + private void buildLayout(JTabbedPane modeTab, JButton saveButton) { JPanel rightPanel = new JPanel(); GroupLayout rightLayout = new GroupLayout(rightPanel); rightPanel.setLayout(rightLayout); rightLayout.setHorizontalGroup( - rightLayout.createParallelGroup(GroupLayout.Alignment.LEADING) - .addComponent(valueTextField, GroupLayout.PREFERRED_SIZE, 90, GroupLayout.PREFERRED_SIZE) - .addGroup(rightLayout.createSequentialGroup() + rightLayout.createSequentialGroup() + .addGroup(rightLayout.createParallelGroup(GroupLayout.Alignment.CENTER) + .addComponent(intensityLabel) .addComponent(intensitySlider) - .addPreferredGap(LayoutStyle.ComponentPlacement.UNRELATED) + ) + .addPreferredGap(LayoutStyle.ComponentPlacement.UNRELATED) + .addGroup(rightLayout.createParallelGroup(GroupLayout.Alignment.CENTER) + .addComponent(orderLabel) + .addComponent(orderSlider) ) ); rightLayout.setVerticalGroup( rightLayout.createSequentialGroup() - .addComponent(valueTextField, GroupLayout.PREFERRED_SIZE, - GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE) + .addGroup(rightLayout.createParallelGroup(GroupLayout.Alignment.BASELINE) + .addComponent(intensityLabel) + .addComponent(orderLabel) + ) .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) .addGroup(rightLayout.createParallelGroup(GroupLayout.Alignment.CENTER) .addComponent(intensitySlider) + .addComponent(orderSlider) ) - .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) ); // ------------------------- @@ -72,10 +75,8 @@ public class NonIdealFilteringView extends AbstractView implements IFilteringVie .addGroup(layout.createSequentialGroup() .addContainerGap() .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) - //.addComponent(valeurMin) .addGap(50) .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) - //.addComponent(valeurMax) .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) .addComponent(saveButton) @@ -117,8 +118,22 @@ public class NonIdealFilteringView extends AbstractView implements IFilteringVie try { this.intensityLabel.setText(String.valueOf(intensitySlider.getPercentage())); switch (modeTab.getSelectedIndex()) { - case 0 -> presenter.computeLowPassFiltering(intensitySlider.getPercentage()); - case 1 -> presenter.computeHighPassFiltering(intensitySlider.getPercentage()); + case 0 -> presenter.computeButterworthLowPassFiltering(intensitySlider.getPercentage(), orderSlider.getValue()); + case 1 -> presenter.computeButterworthHighPassFiltering(intensitySlider.getPercentage(), orderSlider.getValue()); + } + } finally { + isUpdating = false; + } + } + + private void onOrderUpdate(ChangeEvent e) { + if (isUpdating) return; + isUpdating = true; + try { + this.orderLabel.setText(String.valueOf(orderSlider.getValue())); + switch (modeTab.getSelectedIndex()) { + case 0 -> presenter.computeButterworthLowPassFiltering(intensitySlider.getPercentage(), orderSlider.getValue()); + case 1 -> presenter.computeButterworthHighPassFiltering(intensitySlider.getPercentage(), orderSlider.getValue()); } } finally { isUpdating = false; @@ -136,21 +151,26 @@ public class NonIdealFilteringView extends AbstractView implements IFilteringVie } @Override - public void updateButterworthHighPassFiltering(Image image) { + public void updateButterworthLowPassFiltering(Image image) { modeTab.loadImage(LOW_PASS_TAB_TITLE, image); } @Override - public void updateButterworthLowPassFiltering(Image image) { + public void updateButterworthHighPassFiltering(Image image) { modeTab.loadImage(HIGH_PASS_TAB_TITLE, image); } @Override public void initComponents(){ this.intensityLabel = new JLabel(); - this.intensitySlider = new IntensitySlider(); + this.orderLabel = new JLabel(); + + this.intensitySlider = new IntensitySlider(0, 1000); this.intensitySlider.setOrientation(JSlider.VERTICAL); + this.orderSlider = new IntensitySlider(0, 10); + this.orderSlider.setOrientation(JSlider.VERTICAL); + this.modeTab = new TabbedImagePanel(); this.modeTab.addPreviewTab(LOW_PASS_TAB_TITLE); this.modeTab.addPreviewTab(HIGH_PASS_TAB_TITLE); @@ -159,8 +179,8 @@ public class NonIdealFilteringView extends AbstractView implements IFilteringVie saveButton.addActionListener(e -> {}); - buildLayout(modeTab, intensityLabel, intensitySlider, saveButton); - setTitle("Filtering"); + buildLayout(modeTab, saveButton); + setTitle("Filtre non-idéal"); setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); pack(); } @@ -172,14 +192,17 @@ public class NonIdealFilteringView extends AbstractView implements IFilteringVie this.intensitySlider.addChangeListener(this::onCutoffUpdate); this.intensityLabel.setText(String.valueOf(intensitySlider.getValue())); + this.orderSlider.addChangeListener(this::onOrderUpdate); + this.orderLabel.setText(String.valueOf(orderSlider.getValue())); + this.modeTab.addChangeListener(e -> { switch (modeTab.getSelectedIndex()) { - case 0 -> presenter.computeLowPassFiltering(intensitySlider.getPercentage()); - case 1 -> presenter.computeHighPassFiltering(intensitySlider.getPercentage()); + case 0 -> presenter.computeButterworthLowPassFiltering(intensitySlider.getPercentage(), orderSlider.getValue()); + case 1 -> presenter.computeButterworthHighPassFiltering(intensitySlider.getPercentage(), orderSlider.getValue()); } }); - this.presenter.computeLowPassFiltering(intensitySlider.getPercentage()); + this.presenter.computeButterworthLowPassFiltering(intensitySlider.getPercentage(), orderSlider.getValue()); } @Override