Update architecture

This commit is contained in:
2026-09-22 13:30:03 +02:00
parent 8b1e6ae358
commit b177ea5a38
27 changed files with 792 additions and 374 deletions

View File

@@ -5,14 +5,14 @@ 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.FilteringPresenter;
import presenters.doubleMatrix.DoubleMatrixPresenter;
import presenters.filtering.FilteringPresenter;
import presenters.MainPresenter;
import presenters.NavPresenter;
import services.ImageService;
import ui.implementation.components.nav.NavBar;
import ui.implementation.views.DoubleMatrix;
import ui.implementation.views.FilteringView;
import ui.implementation.views.IdealFilteringView;
import ui.implementation.views.MainView;
import ui.abstractions.IDoubleMatrix;
import ui.abstractions.IFilteringView;
@@ -40,7 +40,7 @@ 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(FilteringView.class);
bind(IFilteringView.class).to(IdealFilteringView.class);
//--- Presentation layer ---
bind(MainPresenter.class);

View File

@@ -3,11 +3,9 @@ package app;
import com.google.inject.Inject;
import com.google.inject.Provider;
import ui.implementation.views.DoubleMatrix;
import ui.implementation.views.FilteringView;
import ui.implementation.views.IdealFilteringView;
import ui.implementation.views.ViewInitializer;
import javax.swing.text.View;
/**
* Concrete implement of the INavigator interface.
* Implements abstract endpoints to displays views in a Swing way
@@ -16,12 +14,12 @@ import javax.swing.text.View;
public class SwingNavigator implements INavigator {
private final Provider<DoubleMatrix> doubleMatrixProvider;
private final Provider<FilteringView> filteringProvider;
private final Provider<IdealFilteringView> filteringProvider;
@Inject
public SwingNavigator(
Provider<DoubleMatrix> doubleMatrixProvider,
Provider<FilteringView> filteringProvider
Provider<IdealFilteringView> filteringProvider
) {
this.doubleMatrixProvider = doubleMatrixProvider;
this.filteringProvider = filteringProvider;
@@ -36,7 +34,7 @@ public class SwingNavigator implements INavigator {
@Override
public void showFiltering() {
FilteringView filteringView = filteringProvider.get();
IdealFilteringView filteringView = filteringProvider.get();
ViewInitializer.Initialize(filteringView);
filteringView.setVisible(true);
}

View File

@@ -6,35 +6,47 @@ import com.google.inject.Singleton;
import domain.common.Mode;
import domain.events.ImageChangedEvent;
import domain.events.ModeChangedEvent;
import domain.events.listeners.Listener;
import domain.image.Image;
import java.util.ArrayList;
import java.util.List;
/**
* Represents the global state of the app
*/
@Singleton
public class AppState extends State {
private Mode mode;
private Image rgbImage;
private final List<AppStateListener> listeners = new ArrayList<>();
@Inject
public AppState(EventBus eventBus) {
super(eventBus);
public AppState() {}
public void subscribe(AppStateListener listener) {
listeners.add(listener);
}
public void unsubscribe(AppStateListener listener) {
listeners.remove(listener);
}
public void setMode(Mode mode) {
this.mode = mode;
this.eventBus.post(new ModeChangedEvent(mode));
listeners.forEach(listener -> listener.onModeChanged(mode));
}
public void setImage(Image image) {
this.rgbImage = image;
this.eventBus.post(new ImageChangedEvent(image));
listeners.forEach(listener -> listener.onImageChanged(image));
}
public Image getImage(){
return this.rgbImage;
public Image getImage() {
return rgbImage;
}
public Mode getMode() { return this.mode; }
public Mode getMode() {
return mode;
}
}

View File

@@ -0,0 +1,9 @@
package app.state;
import domain.common.Mode;
import domain.image.Image;
public interface AppStateListener {
void onModeChanged(Mode mode);
void onImageChanged(Image image);
}

View File

@@ -1,22 +1,32 @@
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;
import presenters.doubleMatrix.DoubleMatrixListener;
import java.util.ArrayList;
import java.util.List;
public class DoubleMatrixState extends State {
private ComplexMatrix fourier;
private GrayScaleMatrix module;
private GrayScaleMatrix phase;
private GrayScaleMatrix real;
private GrayScaleMatrix imaginary;
private final List<DoubleMatrixListener> listeners = new ArrayList<>();
@Inject
public DoubleMatrixState() { super(new EventBus()); }
public DoubleMatrixState() {}
public void subscribe(DoubleMatrixListener listener) {
listeners.add(listener);
}
public void unsubscribe(DoubleMatrixListener listener) {
listeners.remove(listener);
}
public ComplexMatrix getFourier() {
return fourier;
@@ -24,26 +34,42 @@ public class DoubleMatrixState extends State {
public void setFourier(ComplexMatrix fourier) {
this.fourier = fourier;
this.eventBus.post(new ComplexMatrixChanged(fourier));
listeners.forEach(listener -> listener.onFourierChanged(fourier));
}
public void setModule(GrayScaleMatrix matrix) {
this.module = matrix;
this.eventBus.post(new ModuleChangedEvent(matrix));
listeners.forEach(listener -> listener.onModuleChanged(matrix));
}
public void setPhase(GrayScaleMatrix matrix) {
this.phase = matrix;
this.eventBus.post(new PhaseChangedEvent(matrix));
listeners.forEach(listener -> listener.onPhaseChanged(matrix));
}
public void setReal(GrayScaleMatrix matrix) {
this.real = matrix;
this.eventBus.post(new RealChangedEvent(matrix));
listeners.forEach(listener -> listener.onRealChanged(matrix));
}
public void setImaginary(GrayScaleMatrix matrix) {
this.imaginary = matrix;
this.eventBus.post(new ImaginaryChangedEvent(matrix));
listeners.forEach(listener -> listener.onImaginaryChanged(matrix));
}
public GrayScaleMatrix getModule() {
return module;
}
public GrayScaleMatrix getPhase() {
return phase;
}
public GrayScaleMatrix getReal() {
return real;
}
public GrayScaleMatrix getImaginary() {
return imaginary;
}
}

View File

@@ -1,27 +1,64 @@
package app.state;
import com.google.common.eventbus.EventBus;
import com.google.inject.Inject;
import domain.events.filtering.HighPassChangedEvent;
import domain.events.filtering.LowPassChangedEvent;
import domain.events.fourier.ModuleChangedEvent;
import domain.image.GrayScaleMatrix;
import domain.image.Image;
import presenters.filtering.FilteringListener;
import java.util.ArrayList;
import java.util.List;
public class FilteringState extends State {
private Image lowPass;
private Image highPass;
private Image butterworthLowPass;
private Image butterworthHighPass;
private final List<FilteringListener> listeners = new ArrayList<>();
@Inject
public FilteringState() { super(new EventBus()); }
public FilteringState() {}
public void subscribe(FilteringListener listener) {
listeners.add(listener);
}
public void unsubscribe(FilteringListener listener) {
listeners.remove(listener);
}
public void setLowPass(Image image) {
this.eventBus.post(new LowPassChangedEvent(image));
lowPass = image;
listeners.forEach(listener -> listener.onLowPassChanged(image));
}
public void setHighPass(Image image) {
this.eventBus.post(new HighPassChangedEvent(image));
highPass = image;
listeners.forEach(listener -> listener.onHighPassChanged(image));
}
}
public void setButterworthLowPass(Image image) {
butterworthLowPass = image;
listeners.forEach(listener -> listener.onButterworthLowPassChanged(image));
}
public void setButterworthHighPass(Image image) {
butterworthHighPass = image;
listeners.forEach(listener -> listener.onButterworthHighPassChanged(image));
}
public Image getLowPass() {
return lowPass;
}
public Image getHighPass() {
return highPass;
}
public Image getButterworthLowPass() {
return butterworthLowPass;
}
public Image getButterworthHighPass() {
return butterworthHighPass;
}
}

View File

@@ -2,6 +2,10 @@ package app.state;
import com.google.common.eventbus.EventBus;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
/**
* Represents any states that could ever exist.
* In order to notify the caller about any change made to internal properties of a children state, the caller
@@ -9,18 +13,4 @@ import com.google.common.eventbus.EventBus;
* @author Laurent Crema
*/
public abstract class State {
protected EventBus eventBus;
public State(EventBus eventBus) {
this.eventBus = eventBus;
}
/**
* Register the caller so it can be notifed from changes within the state
* @param obj the caller itself
*/
public void register(Object obj) {
eventBus.register(obj);
}
}
}

View File

@@ -1,5 +0,0 @@
package domain.events.filtering;
import domain.image.Image;
public record HighPassChangedEvent(Image image) { }

View File

@@ -1,5 +0,0 @@
package domain.events.filtering;
import domain.image.Image;
public record LowPassChangedEvent(Image image) { }

View File

@@ -0,0 +1,5 @@
package domain.events.listeners;
public interface Listener {
}

View File

@@ -13,136 +13,96 @@ import java.util.List;
public class GlobalLinearFiltering {
public static GrayScaleMatrix lowPassFiltering(GrayScaleMatrix matrix, double cutoff) {
int width = matrix.getWidth();
int height = matrix.getHeight();
double minCutoff = 0;
double maxCutoff = Math.sqrt(Math.pow(width / 2.0, 2) + Math.pow(height / 2.0, 2));
validateCutoff(cutoff, width, height);
if(cutoff < minCutoff || cutoff > maxCutoff){
throw new IllegalArgumentException(
String.format("Cutoff value is out of bond (%f - %f)", minCutoff, maxCutoff)
);
}
Mat mat = toMat(matrix);
Mat spectrum = fourierTransform(mat);
Mat mat = new Mat(height, width, CvType.CV_64F);
shiftSpectrum(spectrum);
applyIdealLowPassFilter(spectrum, cutoff);
shiftSpectrum(spectrum);
double[][] data = matrix.getRawData();
Mat filtered = inverseFourierTransform(spectrum);
for (int y = 0; y < height; y++) {
mat.put(y, 0, data[y]);
}
return toGrayScaleMatrix(filtered);
}
// Fourier transform
Mat spectrum = new Mat();
Core.dft(mat, spectrum, Core.DFT_COMPLEX_OUTPUT);
public static GrayScaleMatrix highPassFiltering(GrayScaleMatrix matrix, double cutoff) {
int width = matrix.getWidth();
int height = matrix.getHeight();
validateCutoff(cutoff, width, height);
Mat mat = toMat(matrix);
Mat spectrum = fourierTransform(mat);
shiftSpectrum(spectrum);
applyIdealHighPassFilter(spectrum, cutoff);
shiftSpectrum(spectrum);
Mat filtered = inverseFourierTransform(spectrum);
return toGrayScaleMatrix(filtered);
}
public static GrayScaleMatrix butterworthLowPassFiltering(GrayScaleMatrix matrix, double cutoff, int order) {
validateCutoff(cutoff, matrix.getWidth(), matrix.getHeight());
Mat spectrum = fourierTransform(toMat(matrix));
shiftSpectrum(spectrum);
applyButterworthLowPassFilter(spectrum, cutoff, order);
shiftSpectrum(spectrum);
return toGrayScaleMatrix(inverseFourierTransform(spectrum));
}
public static GrayScaleMatrix butterworthHighPassFiltering(GrayScaleMatrix matrix, double cutoff, int order) {
validateCutoff(cutoff, matrix.getWidth(), matrix.getHeight());
Mat spectrum = fourierTransform(toMat(matrix));
shiftSpectrum(spectrum);
applyButterworthHighPassFilter(spectrum, cutoff, order);
shiftSpectrum(spectrum);
return toGrayScaleMatrix(inverseFourierTransform(spectrum));
}
private static void applyIdealLowPassFilter(Mat spectrum, double cutoff) {
int width = spectrum.cols();
int height = spectrum.rows();
// Shift zero frequency to the center
int cx = width / 2;
int cy = height / 2;
Mat q0 = spectrum.submat(0, cy, 0, cx);
Mat q1 = spectrum.submat(0, cy, cx, width);
Mat q2 = spectrum.submat(cy, height, 0, cx);
Mat q3 = spectrum.submat(cy, height, cx, width);
Mat tmp = new Mat();
q0.copyTo(tmp);
q3.copyTo(q0);
tmp.copyTo(q3);
q1.copyTo(tmp);
q2.copyTo(q1);
tmp.copyTo(q2);
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
double distance = Math.sqrt(Math.pow(x - cx, 2) + Math.pow(y - cy, 2));
double distance = Math.sqrt(
Math.pow(x - cx, 2) + Math.pow(y - cy, 2)
);
if (distance > cutoff) {
spectrum.put(y, x, 0, 0);
}
}
}
// Shift back
q0.copyTo(tmp);
q3.copyTo(q0);
tmp.copyTo(q3);
q1.copyTo(tmp);
q2.copyTo(q1);
tmp.copyTo(q2);
// Inverse Fourier transform
Mat filtered = new Mat();
Core.idft(
spectrum,
filtered,
Core.DFT_REAL_OUTPUT | Core.DFT_SCALE
);
// Convert result back to your Image
double[][] pixels = new double[height][width];
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
pixels[y][x] = Math.clamp(filtered.get(y, x)[0], 0.0, 255.0);
}
}
return new GrayScaleMatrix(pixels);
}
public static GrayScaleMatrix highPassFiltering(GrayScaleMatrix matrix, double cutoff){
int width = matrix.getWidth();
int height = matrix.getHeight();
private static void applyIdealHighPassFilter(Mat spectrum, double cutoff) {
int width = spectrum.cols();
int height = spectrum.rows();
double minCutoff = 0;
double maxCutoff = Math.sqrt(Math.pow(width / 2.0, 2) + Math.pow(height / 2.0, 2));
if(cutoff < minCutoff || cutoff > maxCutoff){
throw new IllegalArgumentException(
String.format("Cutoff value is out of bond (%f - %f)", minCutoff, maxCutoff)
);
}
Mat mat = new Mat(height, width, CvType.CV_64F);
double[][] data = matrix.getRawData();
for (int y = 0; y < height; y++) {
mat.put(y, 0, data[y]);
}
// Fourier transform
Mat spectrum = new Mat();
Core.dft(mat, spectrum, Core.DFT_COMPLEX_OUTPUT);
// Shift zero frequency to the center
int cx = width / 2;
int cy = height / 2;
Mat q0 = spectrum.submat(0, cy, 0, cx);
Mat q1 = spectrum.submat(0, cy, cx, width);
Mat q2 = spectrum.submat(cy, height, 0, cx);
Mat q3 = spectrum.submat(cy, height, cx, width);
Mat tmp = new Mat();
q0.copyTo(tmp);
q3.copyTo(q0);
tmp.copyTo(q3);
q1.copyTo(tmp);
q2.copyTo(q1);
tmp.copyTo(q2);
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
double distance = Math.sqrt(Math.pow(x - cx, 2) + Math.pow(y - cy, 2));
if (distance <= cutoff) {
@@ -150,8 +110,94 @@ public class GlobalLinearFiltering {
}
}
}
}
private static void applyButterworthLowPassFilter(Mat spectrum, double cutoff, int order) {
int width = spectrum.cols();
int height = spectrum.rows();
int cx = width / 2;
int cy = height / 2;
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
double distance = Math.sqrt(Math.pow(x - cx, 2) + Math.pow(y - cy, 2) );
double coefficient = 1.0 / (1.0 + Math.pow(distance / cutoff, 2 * order));
double[] value = spectrum.get(y, x);
spectrum.put(y, x, value[0] * coefficient, value[1] * coefficient);
}
}
}
private static void applyButterworthHighPassFilter(Mat spectrum, double cutoff, int order) {
int width = spectrum.cols();
int height = spectrum.rows();
int cx = width / 2;
int cy = height / 2;
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
double distance = Math.sqrt(Math.pow(x - cx, 2) + Math.pow(y - cy, 2));
double coefficient;
if (distance == 0) {
coefficient = 0;
} else {
coefficient = 1.0 / (1.0 + Math.pow(cutoff / distance, 2 * order));
}
double[] value = spectrum.get(y, x);
spectrum.put(y, x, value[0] * coefficient, value[1] * coefficient);
}
}
}
private static void validateCutoff(double cutoff, int width, int height) {
double minCutoff = 0;
double maxCutoff = Math.sqrt(
Math.pow(width / 2.0, 2) +
Math.pow(height / 2.0, 2)
);
if (cutoff < minCutoff || cutoff > maxCutoff) {
throw new IllegalArgumentException(
String.format(
"Cutoff value is out of bounds (%f - %f)",
minCutoff,
maxCutoff
)
);
}
}
private static Mat fourierTransform(Mat mat) {
Mat spectrum = new Mat();
Core.dft(mat, spectrum, Core.DFT_COMPLEX_OUTPUT);
return spectrum;
}
private static void shiftSpectrum(Mat spectrum) {
int width = spectrum.cols();
int height = spectrum.rows();
int cx = width / 2;
int cy = height / 2;
Mat q0 = spectrum.submat(0, cy, 0, cx);
Mat q1 = spectrum.submat(0, cy, cx, width);
Mat q2 = spectrum.submat(cy, height, 0, cx);
Mat q3 = spectrum.submat(cy, height, cx, width);
Mat tmp = new Mat();
// Shift back
q0.copyTo(tmp);
q3.copyTo(q0);
tmp.copyTo(q3);
@@ -159,8 +205,9 @@ public class GlobalLinearFiltering {
q1.copyTo(tmp);
q2.copyTo(q1);
tmp.copyTo(q2);
}
// Inverse Fourier transform
private static Mat inverseFourierTransform(Mat spectrum) {
Mat filtered = new Mat();
Core.idft(
@@ -169,23 +216,41 @@ public class GlobalLinearFiltering {
Core.DFT_REAL_OUTPUT | Core.DFT_SCALE
);
// Convert result back to your Image
return filtered;
}
private static GrayScaleMatrix toGrayScaleMatrix(Mat filtered) {
int width = filtered.cols();
int height = filtered.rows();
double[][] pixels = new double[height][width];
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
pixels[y][x] = Math.clamp(filtered.get(y, x)[0], 0.0, 255.0);
pixels[y][x] = Math.clamp(
filtered.get(y, x)[0],
0.0,
255.0
);
}
}
return new GrayScaleMatrix(pixels);
}
public static void butterworthLowPassFiltering(){
private static Mat toMat(GrayScaleMatrix matrix) {
int width = matrix.getWidth();
int height = matrix.getHeight();
Mat mat = new Mat(height, width, CvType.CV_64F);
double[][] data = matrix.getRawData();
for (int y = 0; y < height; y++) {
mat.put(y, 0, data[y]);
}
return mat;
}
public static void butterworthHighPassFiltering(){
}
}

View File

@@ -1,55 +0,0 @@
package presenters;
import app.state.FilteringState;
import com.google.common.eventbus.Subscribe;
import com.google.inject.Inject;
import domain.events.filtering.HighPassChangedEvent;
import domain.events.filtering.LowPassChangedEvent;
import domain.image.GrayScaleMatrix;
import domain.image.Image;
import services.FilteringService;
import services.ImageService;
import ui.abstractions.IFilteringView;
public class FilteringPresenter {
private final FilteringState state;
private final ImageService imageService;
private final FilteringService filteringService;
private IFilteringView view;
@Inject
public FilteringPresenter(FilteringState state, ImageService imageService, FilteringService filteringService) {
this.state = state;
this.state.register(this);
this.imageService = imageService;
this.filteringService = filteringService;
}
public void setView(IFilteringView view){
this.view = view;
}
public void computeLowPassFiltering(int percentage){
GrayScaleMatrix matrix = imageService.getGrayScale();
Image image = filteringService.lowPassFiltering(matrix, (double) percentage /100);
this.state.setLowPass(image);
}
public void computeHighPassFiltering(int percentage){
GrayScaleMatrix matrix = imageService.getGrayScale();
Image image = filteringService.highPassFiltering(matrix, (double) percentage /100);
this.state.setHighPass(image);
}
@Subscribe
public void onLowPassUpdate(LowPassChangedEvent event){
view.updateLowPassFiltering(event.image());
}
@Subscribe
public void onHighPassUpdate(HighPassChangedEvent event){
view.updateHighPassFiltering(event.image());
}
}

View File

@@ -1,67 +1,63 @@
package presenters;
import com.google.common.eventbus.EventBus;
import com.google.common.eventbus.Subscribe;
import app.state.AppState;
import app.state.AppStateListener;
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.abstractions.IMainView;
/**
* @author Laurent Crema
*/
public class MainPresenter {
public class MainPresenter implements AppStateListener {
private IMainView view;
private final AppState appState;
private final ModeService modeService;
private final ImageService imageService;
@Inject
public MainPresenter(ModeService modeService, ImageService imageService, EventBus eventBus) {
public MainPresenter(AppState appState, ModeService modeService, ImageService imageService) {
this.appState = appState;
this.modeService = modeService;
this.imageService = imageService;
eventBus.register(this);
this.appState.subscribe(this);
}
public void setView(IMainView view) {
this.view = view;
}
@Subscribe
public void onImageChanged(ImageChangedEvent e){
view.displayImage(e.image());
@Override
public void onImageChanged(Image image) {
view.displayImage(image);
}
@Subscribe
public void onModeChanged(ModeChangedEvent e){
view.changeMode(e.mode());
@Override
public void onModeChanged(Mode mode) {
view.changeMode(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();
public void drawPixel(int x, int y, int red, int green, int blue) {
if (modeService.getMode() != Mode.DRAW_PIXEL) return;
Image image = appState.getImage();
image.setPixelColor(x, y, red, green, blue);
imageService.setImage(image);
appState.setImage(image);
}
public void drawShape(int x1, int x2, int y1, int y2, int red, int green, int blue){
Image image = imageService.getImage();
public void drawShape(int x1, int x2, int y1, int y2, int red, int green, int blue) {
Image image = appState.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;
case DRAW_RECTANGLE -> image.setRectangleColor(x1, x2, y1, y2, red, green, blue);
case DRAW_LINE -> image.setLineColor(x1, x2, y1, y2, red, green, blue);
case DRAW_CIRCLE -> {}
}
imageService.setImage(image);
appState.setImage(image);
}
}
public void dispose() {
appState.unsubscribe(this);
}
}

View File

@@ -1,9 +1,11 @@
package presenters;
import app.INavigator;
import app.state.AppState;
import com.google.inject.Inject;
import domain.common.Mode;
import domain.image.GrayScaleMatrix;
import domain.image.Image;
import services.ImageService;
import services.ModeService;
import ui.abstractions.INavBar;
@@ -11,19 +13,17 @@ import ui.abstractions.INavBar;
import java.io.File;
import java.io.IOException;
/**
* @author Laurent Crema
*/
public class NavPresenter {
private INavBar navBar;
private INavigator navigator;
private final INavigator navigator;
private final AppState appState;
private final ImageService imageService;
private final ModeService modeService;
@Inject
public NavPresenter(INavigator navigator, ImageService imageService, ModeService modeService) {
public NavPresenter(INavigator navigator, AppState appState, ImageService imageService, ModeService modeService) {
this.navigator = navigator;
this.appState = appState;
this.imageService = imageService;
this.modeService = modeService;
}
@@ -33,27 +33,28 @@ public class NavPresenter {
}
public void createImage(int red, int green, int blue, int height, int width) {
imageService.createImage(red, green, blue, height, width);
Image image = imageService.createImage(red, green, blue, height, width);
appState.setImage(image);
}
public void loadImage(File f) throws IOException {
imageService.loadImage(f);
public void loadImage(File file) throws IOException {
Image image = imageService.loadImage(file);
appState.setImage(image);
}
public void goToFourier(){
public void goToFourier() {
navigator.showFourier();
}
public void goToFiltering(){
public void goToFiltering() {
navigator.showFiltering();
}
public GrayScaleMatrix getImageGrayMatrix() {
//return ((CImageNG)appState.getImage()).getMatrice();
return imageService.getImage().toGrayScale();
return appState.getImage().toGrayScale();
}
public void setMode(Mode mode) {
modeService.setMode(mode);
}
}
}

View File

@@ -0,0 +1,12 @@
package presenters.doubleMatrix;
import domain.image.GrayScaleMatrix;
import domain.image.processing.complex.ComplexMatrix;
public interface DoubleMatrixListener {
void onFourierChanged(ComplexMatrix fourier);
void onModuleChanged(GrayScaleMatrix matrix);
void onPhaseChanged(GrayScaleMatrix matrix);
void onRealChanged(GrayScaleMatrix matrix);
void onImaginaryChanged(GrayScaleMatrix matrix);
}

View File

@@ -1,5 +1,6 @@
package presenters;
package presenters.doubleMatrix;
import app.state.AppState;
import app.state.DoubleMatrixState;
import com.google.common.eventbus.Subscribe;
import com.google.inject.Inject;
@@ -18,25 +19,26 @@ import java.io.IOException;
/**
* @author Laurent Crema
*/
public class DoubleMatrixPresenter {
public class DoubleMatrixPresenter implements DoubleMatrixListener {
private IDoubleMatrix view;
private final AppState appState;
private final DoubleMatrixState state;
private final ImageService imageService;
@Inject
public DoubleMatrixPresenter(ImageService imageService, DoubleMatrixState state) {
public DoubleMatrixPresenter(AppState appState, ImageService imageService, DoubleMatrixState state) {
this.appState = appState;
this.imageService = imageService;
this.state = state;
this.state.register(this);
this.state.subscribe(this);
}
public void setView(IDoubleMatrix view){
public void setView(IDoubleMatrix view) {
this.view = view;
}
public void saveImage(Image image) throws IOException {
this.imageService.saveImage(image);
imageService.saveImage(image);
}
public void loadModule(){
@@ -87,28 +89,32 @@ public class DoubleMatrixPresenter {
state.setImaginary(matrix);
}
@Subscribe
public void onModuleUpdate(ModuleChangedEvent event) {
@Override
public void onModuleChanged(GrayScaleMatrix matrix) {
GrayScaleMatrix module = new GrayScaleMatrix(getFourier().getModule());
view.updateModule(module, event.matrix());
view.updateModule(module, matrix);
}
@Subscribe
public void onPhaseUpdate(PhaseChangedEvent event) {
@Override
public void onPhaseChanged(GrayScaleMatrix matrix) {
GrayScaleMatrix phase = new GrayScaleMatrix(getFourier().getPhase());
view.updatePhase(phase, event.matrix());
view.updatePhase(phase, matrix);
}
@Subscribe
public void onRealUpdate(RealChangedEvent event) {
@Override
public void onRealChanged(GrayScaleMatrix matrix) {
GrayScaleMatrix real = new GrayScaleMatrix(getFourier().getPartieReelle());
view.updateReal(real, event.matrix());
view.updateReal(real, matrix);
}
@Subscribe
public void onImaginaryUpdate(ImaginaryChangedEvent event) {
@Override
public void onFourierChanged(ComplexMatrix fourier) {
}
@Override
public void onImaginaryChanged(GrayScaleMatrix matrix) {
GrayScaleMatrix imaginary = new GrayScaleMatrix(getFourier().getPartieImaginaire());
view.updateImaginary(imaginary, event.matrix());
view.updateImaginary(imaginary, matrix);
}
private GrayScaleMatrix clip(GrayScaleMatrix matrix, double black, double white){
@@ -119,7 +125,7 @@ public class DoubleMatrixPresenter {
private ComplexMatrix getFourier() {
if (state.getFourier() == null) {
state.setFourier(imageService.computeFourier());
state.setFourier(imageService.computeFourier(appState.getImage()));
}
return state.getFourier();
}

View File

@@ -0,0 +1,11 @@
package presenters.filtering;
import domain.events.listeners.Listener;
import domain.image.Image;
public interface FilteringListener extends Listener {
void onLowPassChanged(Image image);
void onHighPassChanged(Image image);
void onButterworthLowPassChanged(Image image);
void onButterworthHighPassChanged(Image image);
}

View File

@@ -0,0 +1,79 @@
package presenters.filtering;
import app.state.AppState;
import app.state.FilteringState;
import com.google.inject.Inject;
import domain.image.GrayScaleMatrix;
import domain.image.Image;
import services.FilteringService;
import services.ImageService;
import ui.abstractions.IFilteringView;
public class FilteringPresenter implements FilteringListener {
private final AppState appState;
private final FilteringState state;
private final ImageService imageService;
private final FilteringService filteringService;
private IFilteringView view;
@Inject
public FilteringPresenter(AppState appState, FilteringState state, ImageService imageService, FilteringService filteringService) {
this.appState = appState;
this.state = state;
this.imageService = imageService;
this.filteringService = filteringService;
this.state.subscribe(this);
}
public void setView(IFilteringView view) {
this.view = view;
}
public void computeLowPassFiltering(float percentage) {
GrayScaleMatrix matrix = imageService.getGrayScale(appState.getImage());
Image image = filteringService.lowPassFiltering(matrix, percentage / 100);
state.setLowPass(image);
}
public void computeHighPassFiltering(float percentage) {
GrayScaleMatrix matrix = imageService.getGrayScale(appState.getImage());
Image image = filteringService.highPassFiltering(matrix, percentage / 100);
state.setHighPass(image);
}
public void computeButterworthLowPassFiltering(float percentage) {
GrayScaleMatrix matrix = imageService.getGrayScale(appState.getImage());
Image image = filteringService.butterworthLowPassFiltering(matrix, percentage / 100);
state.setButterworthLowPass(image);
}
public void computeButterworthHighPassFiltering(float percentage) {
GrayScaleMatrix matrix = imageService.getGrayScale(appState.getImage());
Image image = filteringService.butterworthHighPassFiltering(matrix, percentage / 100);
state.setButterworthHighPass(image);
}
@Override
public void onLowPassChanged(Image image) {
view.updateLowPassFiltering(image);
}
@Override
public void onHighPassChanged(Image image) {
view.updateHighPassFiltering(image);
}
@Override
public void onButterworthLowPassChanged(Image image) {
view.updateButterworthLowPassFiltering(image);
}
@Override
public void onButterworthHighPassChanged(Image image) {
view.updateButterworthHighPassFiltering(image);
}
public void dispose() {
state.unsubscribe(this);
}
}

View File

@@ -22,4 +22,11 @@ public class FilteringService {
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;
}
}

View File

@@ -1,9 +1,7 @@
package services;
import app.state.AppState;
import com.google.inject.Inject;
import domain.image.Image;
import domain.image.GrayScaleMatrix;
import domain.image.Image;
import domain.image.processing.complex.ComplexMatrix;
import domain.image.processing.fourier.Fourier;
import infrastructure.image.io.ImageLoader;
@@ -17,42 +15,25 @@ import java.io.IOException;
*/
public class ImageService {
private final AppState appState;
@Inject
public ImageService(AppState appState) {
this.appState = appState;
public Image createImage(int red, int green, int blue, int height, int width) {
return new Image(red, green, blue, height, width);
}
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 Image loadImage(File file) throws IOException {
return ImageLoader.loadImage(file);
}
public void saveImage(Image image) throws IOException {
ImageSaver.saveImage(image);
}
public ComplexMatrix computeFourier(){
GrayScaleMatrix matrix = appState.getImage().toGrayScale();
public ComplexMatrix computeFourier(Image image) {
GrayScaleMatrix matrix = image.toGrayScale();
ComplexMatrix fourier = Fourier.Fourier2D(matrix.getRawData());
return Fourier.decroise(fourier);
}
public Image getImage(){
return appState.getImage();
public GrayScaleMatrix getGrayScale(Image image) {
return image.toGrayScale();
}
public void setImage(Image img){
appState.setImage(img);
}
public GrayScaleMatrix getGrayScale(){
return appState.getImage().toGrayScale();
}
}
}

View File

@@ -5,4 +5,11 @@ import javax.swing.*;
public abstract class AbstractView extends JFrame {
public abstract void initComponents();
public abstract void initValues();
public abstract void disposeCore();
@Override
public void dispose() {
disposeCore();
super.dispose();
}
}

View File

@@ -0,0 +1,17 @@
package ui.implementation.components.controls;
import javax.swing.*;
import java.awt.event.ActionListener;
public class IntensitySlider extends JSlider {
public IntensitySlider() {
super();
setMinimum(0);
setMaximum(1000);
}
public float getPercentage(){
return (float) getValue() /10;
}
}

View File

@@ -1,4 +1,4 @@
package ui.implementation.components.button;
package ui.implementation.components.controls;
import javax.swing.*;
import java.net.URL;

View File

@@ -0,0 +1,29 @@
package ui.implementation.components.image;
import domain.image.Image;
import infrastructure.ui.ImageMapper;
import javax.swing.*;
import java.awt.image.BufferedImage;
import java.util.HashMap;
import java.util.Map;
public class TabbedImagePanel extends JTabbedPane {
private final Map<String, ImagePanel> tabs;
public TabbedImagePanel() {
this.tabs = new HashMap<>();
}
public void addPreviewTab(String title) {
ImagePanel panel = new ImagePanel();
tabs.put(title, panel);
addTab(title, panel);
}
public void loadImage(String title, Image image) {
BufferedImage bufferedImage = ImageMapper.toBufferedImage(image);
tabs.get(title).loadImage(bufferedImage);
}
}

View File

@@ -2,9 +2,9 @@ package ui.implementation.views;
import domain.image.GrayScaleMatrix;
import jakarta.inject.Inject;
import presenters.DoubleMatrixPresenter;
import presenters.doubleMatrix.DoubleMatrixPresenter;
import ui.abstractions.AbstractView;
import ui.implementation.components.button.SaveButton;
import ui.implementation.components.controls.SaveButton;
import ui.implementation.components.image.ImagePanel;
import ui.implementation.components.intent.FileChooser;
import ui.abstractions.IDoubleMatrix;
@@ -96,6 +96,11 @@ public class DoubleMatrix extends AbstractView implements IDoubleMatrix
this.whiteTextField.addActionListener(this::jTextFieldBlancActionPerformed);
}
@Override
public void disposeCore() {
this.presenter.setView(null);
}
private JSlider buildSlider() {
return new JSlider(JSlider.VERTICAL);
}

View File

@@ -2,39 +2,42 @@ package ui.implementation.views;
import com.google.inject.Inject;
import domain.image.Image;
import infrastructure.ui.ImageMapper;
import presenters.FilteringPresenter;
import presenters.filtering.FilteringPresenter;
import ui.abstractions.AbstractView;
import ui.implementation.components.button.SaveButton;
import ui.implementation.components.image.ImagePanel;
import ui.implementation.components.controls.IntensitySlider;
import ui.implementation.components.controls.SaveButton;
import ui.abstractions.IFilteringView;
import ui.implementation.components.image.TabbedImagePanel;
import javax.swing.*;
import javax.swing.event.ChangeEvent;
public class FilteringView extends AbstractView implements IFilteringView {
public class IdealFilteringView extends AbstractView implements IFilteringView {
private ImagePanel lowPassContainer;
private ImagePanel highPassContainer;
private JTabbedPane modeTab;
private JSlider intensitySlider;
private final static String LOW_PASS_TAB_TITLE = "Passe-haut";
private final static String HIGH_PASS_TAB_TITLE = "Passe-bas";
private TabbedImagePanel modeTab;
private IntensitySlider intensitySlider;
private JLabel intensityLabel;
private final FilteringPresenter presenter;
private boolean isUpdating;
@Inject
public FilteringView(FilteringPresenter presenter) {
public IdealFilteringView(FilteringPresenter presenter) {
this.presenter = presenter;
}
@Override
public void initComponents(){
this.lowPassContainer = new ImagePanel();
this.highPassContainer = new ImagePanel();
this.modeTab = buildModeTab();
this.intensitySlider = new JSlider(0,100,0);
this.intensitySlider.setOrientation(JSlider.VERTICAL);
this.intensityLabel = new JLabel();
this.intensitySlider = new IntensitySlider();
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 -> {});
@@ -45,33 +48,7 @@ public class FilteringView extends AbstractView implements IFilteringView {
pack();
}
@Override
public void initValues(){
this.presenter.setView(this);
this.intensitySlider.setMinimum(0);
this.intensitySlider.setMaximum(100);
this.intensitySlider.setValue(50);
this.intensitySlider.addChangeListener(this::onCutoffUpdate);
this.intensityLabel.setText(String.valueOf(intensitySlider.getValue()));
this.modeTab.addChangeListener(e -> {
switch (modeTab.getSelectedIndex()) {
case 0 -> presenter.computeLowPassFiltering(intensitySlider.getValue());
case 1 -> presenter.computeHighPassFiltering(intensitySlider.getValue());
}
});
this.presenter.computeLowPassFiltering(intensitySlider.getValue());
}
private JTabbedPane buildModeTab(){
JTabbedPane modeTabs = new JTabbedPane();
modeTabs.addTab("Passe-bas", lowPassContainer);
modeTabs.addTab("Passe-haut", highPassContainer);
return modeTabs;
}
private void buildLayout(JTabbedPane modeTab,
JLabel valueTextField,
@@ -158,10 +135,10 @@ public class FilteringView extends AbstractView implements IFilteringView {
if (isUpdating) return;
isUpdating = true;
try {
this.intensityLabel.setText(String.valueOf(intensitySlider.getValue()));
this.intensityLabel.setText(String.valueOf(intensitySlider.getPercentage()));
switch (modeTab.getSelectedIndex()) {
case 0 -> presenter.computeLowPassFiltering(intensitySlider.getValue());
case 1 -> presenter.computeHighPassFiltering(intensitySlider.getValue());
case 0 -> presenter.computeLowPassFiltering(intensitySlider.getPercentage());
case 1 -> presenter.computeHighPassFiltering(intensitySlider.getPercentage());
}
} finally {
isUpdating = false;
@@ -170,12 +147,12 @@ public class FilteringView extends AbstractView implements IFilteringView {
@Override
public void updateLowPassFiltering(Image image) {
lowPassContainer.loadImage(ImageMapper.toBufferedImage(image));
modeTab.loadImage(LOW_PASS_TAB_TITLE, image);
}
@Override
public void updateHighPassFiltering(Image image) {
highPassContainer.loadImage(ImageMapper.toBufferedImage(image));
modeTab.loadImage(HIGH_PASS_TAB_TITLE, image);
}
@Override
@@ -187,4 +164,27 @@ public class FilteringView extends AbstractView implements IFilteringView {
public void updateButterworthLowPassFiltering(Image image) {
}
@Override
public void initValues(){
this.presenter.setView(this);
this.intensitySlider.addChangeListener(this::onCutoffUpdate);
this.intensityLabel.setText(String.valueOf(intensitySlider.getValue()));
this.modeTab.addChangeListener(e -> {
switch (modeTab.getSelectedIndex()) {
case 0 -> presenter.computeLowPassFiltering(intensitySlider.getPercentage());
case 1 -> presenter.computeHighPassFiltering(intensitySlider.getPercentage());
}
});
this.presenter.computeLowPassFiltering(intensitySlider.getPercentage());
}
@Override
public void disposeCore() {
this.presenter.setView(null);
this.presenter.dispose();
}
}

View File

@@ -0,0 +1,190 @@
package ui.implementation.views;
import com.google.inject.Inject;
import domain.image.Image;
import presenters.filtering.FilteringPresenter;
import ui.abstractions.AbstractView;
import ui.abstractions.IFilteringView;
import ui.implementation.components.controls.IntensitySlider;
import ui.implementation.components.controls.SaveButton;
import ui.implementation.components.image.TabbedImagePanel;
import javax.swing.*;
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 TabbedImagePanel modeTab;
private IntensitySlider intensitySlider;
private JLabel intensityLabel;
private final FilteringPresenter presenter;
private boolean isUpdating;
@Inject
public NonIdealFilteringView(FilteringPresenter presenter) {
this.presenter = presenter;
}
private void buildLayout(JTabbedPane modeTab,
JLabel valueTextField,
JSlider intensitySlider,
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()
.addComponent(intensitySlider)
.addPreferredGap(LayoutStyle.ComponentPlacement.UNRELATED)
)
);
rightLayout.setVerticalGroup(
rightLayout.createSequentialGroup()
.addComponent(valueTextField, GroupLayout.PREFERRED_SIZE,
GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE)
.addPreferredGap(LayoutStyle.ComponentPlacement.RELATED)
.addGroup(rightLayout.createParallelGroup(GroupLayout.Alignment.CENTER)
.addComponent(intensitySlider)
)
.addPreferredGap(LayoutStyle.ComponentPlacement.RELATED)
);
// -------------------------
// MAIN LAYOUT
// -------------------------
GroupLayout layout = new GroupLayout(getContentPane());
getContentPane().setLayout(layout);
layout.setHorizontalGroup(
layout.createParallelGroup(GroupLayout.Alignment.LEADING)
// TOP BAR
.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)
.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(saveButton)
)
.addPreferredGap(LayoutStyle.ComponentPlacement.RELATED)
// MAIN CONTENT
.addGroup(layout.createParallelGroup(GroupLayout.Alignment.LEADING)
.addComponent(modeTab)
.addComponent(rightPanel)
)
);
}
private void onCutoffUpdate(ChangeEvent e) {
if (isUpdating) return;
isUpdating = true;
try {
this.intensityLabel.setText(String.valueOf(intensitySlider.getPercentage()));
switch (modeTab.getSelectedIndex()) {
case 0 -> presenter.computeLowPassFiltering(intensitySlider.getPercentage());
case 1 -> presenter.computeHighPassFiltering(intensitySlider.getPercentage());
}
} finally {
isUpdating = false;
}
}
@Override
public void updateLowPassFiltering(Image image) {
}
@Override
public void updateHighPassFiltering(Image image) {
}
@Override
public void updateButterworthHighPassFiltering(Image image) {
modeTab.loadImage(LOW_PASS_TAB_TITLE, image);
}
@Override
public void updateButterworthLowPassFiltering(Image image) {
modeTab.loadImage(HIGH_PASS_TAB_TITLE, image);
}
@Override
public void initComponents(){
this.intensityLabel = new JLabel();
this.intensitySlider = new IntensitySlider();
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("Filtering");
setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
pack();
}
@Override
public void initValues(){
this.presenter.setView(this);
this.intensitySlider.addChangeListener(this::onCutoffUpdate);
this.intensityLabel.setText(String.valueOf(intensitySlider.getValue()));
this.modeTab.addChangeListener(e -> {
switch (modeTab.getSelectedIndex()) {
case 0 -> presenter.computeLowPassFiltering(intensitySlider.getPercentage());
case 1 -> presenter.computeHighPassFiltering(intensitySlider.getPercentage());
}
});
this.presenter.computeLowPassFiltering(intensitySlider.getPercentage());
}
@Override
public void disposeCore() {
this.presenter.setView(null);
this.presenter.dispose();
}
}