waaa
This commit is contained in:
128
src/GUI.py
128
src/GUI.py
@@ -3,12 +3,15 @@ from tkinter import filedialog, colorchooser, messagebox
|
||||
from PIL import ImageTk, ImageDraw, Image
|
||||
from ImageContainer import ImageContainer
|
||||
from ImageManipulation.ManipulationList import *
|
||||
from ImageManipulation.Filters import GaussianBlur, SobelEdge, BinaryThreshold, HistogramThreshold
|
||||
from ImageManipulation.ZoomImage import ZoomImage
|
||||
from SelectionArea import SelectionArea
|
||||
from utils.area_selection import AreaSelectionHandler
|
||||
from utils.brush import BrushHandler
|
||||
from utils.text_entry import TextEntryHandler
|
||||
from utils.file_operations import FileOperationsHandler
|
||||
from utils.image_properties import ImagePropertiesHandler
|
||||
from utils.filter_params import FilterParamsHandler
|
||||
from functools import partial
|
||||
|
||||
|
||||
@@ -39,6 +42,9 @@ class GUI:
|
||||
# Image properties handler (will be initialized in initialise())
|
||||
_imagePropertiesHandler = None
|
||||
|
||||
# Filter parameters handler (will be initialized in initialise())
|
||||
_filterParamsHandler = None
|
||||
|
||||
_canvasImageWidth = None # Store canvas image dimensions
|
||||
_canvasImageHeight = None
|
||||
|
||||
@@ -105,18 +111,25 @@ class GUI:
|
||||
select_menu = tk.Menu(image_menu, tearoff=0)
|
||||
image_menu.add_cascade(label="Select", menu=select_menu)
|
||||
select_menu.add_command(label="Rectangular Selection", command=lambda: self._setRectangularSelection())
|
||||
select_menu.add_command(label="Free-form Selection (Lasso)", command=lambda: self._setLassoSelection())
|
||||
select_menu.add_command(label="Circular Selection", command=lambda: self._setCircularSelection())
|
||||
select_menu.add_command(label="Free-form Selection (Lasso)", command=lambda: self._setLassoSelection())
|
||||
select_menu.add_command(label="Polygon Selection", command=lambda: self._setPolygonSelection())
|
||||
|
||||
image_menu.add_separator()
|
||||
image_menu.add_command(label="Crop", command=lambda: self._cropImage())
|
||||
image_menu.add_command(label="Resize", command=lambda: self._resizeImage())
|
||||
filter_menu = tk.Menu(menu, tearoff=0)
|
||||
menu.add_cascade(label="Filter", menu=filter_menu)
|
||||
filter_menu.add_command(label="Gaussian Filter", command=lambda: self._applyGaussianFilter())
|
||||
filter_menu.add_command(label="Sobel Filter", command=lambda: self._applySobelFilter())
|
||||
filter_menu.add_command(label="Binary Filter", command=lambda: self._applyBinaryFilter())
|
||||
filter_menu.add_command(label="Histogram Thresholding", command=lambda: self._applyHistogramThreshold())
|
||||
clipboard_menu = tk.Menu(menu, tearoff=0)
|
||||
menu.add_cascade(label="Clipboard", menu=clipboard_menu)
|
||||
tools_menu = tk.Menu(menu, tearoff=0)
|
||||
menu.add_cascade(label="Tools", menu=tools_menu)
|
||||
tools_menu.add_command(label="Zoom In", accelerator="Ctrl+Plus", command=lambda: self._zoomIn())
|
||||
tools_menu.add_command(label="Zoom Out", accelerator="Ctrl+Minus", command=lambda: self._zoomOut())
|
||||
shapes_menu = tk.Menu(menu, tearoff=0)
|
||||
menu.add_cascade(label="Shapes", menu=shapes_menu)
|
||||
colors_menu = tk.Menu(menu, tearoff=0)
|
||||
@@ -143,6 +156,7 @@ class GUI:
|
||||
self._imageCanvas.bind("<Button-1>", self._onMouseClick)
|
||||
self._imageCanvas.bind("<B1-Motion>", self._onMouseDrag)
|
||||
self._imageCanvas.bind("<ButtonRelease-1>", self._onMouseRelease)
|
||||
self._imageCanvas.bind("<Double-Button-1>", self._onDoubleClick) # Double-click for polygon completion
|
||||
self._imageCanvas.bind("<Button-3>", self._onRightClick) # Right-click for context menu
|
||||
|
||||
# Settings panel for brush controls
|
||||
@@ -207,6 +221,9 @@ class GUI:
|
||||
self._imagePropertiesHandler = ImagePropertiesHandler(
|
||||
get_current_image=get_current_image
|
||||
)
|
||||
|
||||
# Filter parameters handler from utils/filter_params.py
|
||||
self._filterParamsHandler = FilterParamsHandler(root)
|
||||
|
||||
# Key bindings
|
||||
root.bind_all('<Control-z>', lambda event: self._undo())
|
||||
@@ -219,6 +236,9 @@ class GUI:
|
||||
root.bind_all('<Control-Shift-S>', lambda event: self._toggleSelectionMode())
|
||||
root.bind_all('<Control-Shift-C>', lambda event: self._toggleSelectionShape())
|
||||
root.bind_all('<Control-b>', lambda event: self._toggleBrushMode() if self._brushHandler else None)
|
||||
root.bind_all('<Control-plus>', lambda event: self._zoomIn())
|
||||
root.bind_all('<Control-equal>', lambda event: self._zoomIn()) # Plus key without shift
|
||||
root.bind_all('<Control-minus>', lambda event: self._zoomOut())
|
||||
|
||||
root.mainloop()
|
||||
|
||||
@@ -332,6 +352,16 @@ class GUI:
|
||||
if self._areaSelectionHandler:
|
||||
self._areaSelectionHandler.set_lasso_selection()
|
||||
|
||||
def _setPolygonSelection(self) -> None:
|
||||
"""Set selection to polygon mode."""
|
||||
if self._areaSelectionHandler:
|
||||
self._areaSelectionHandler.set_polygon_selection()
|
||||
|
||||
def _onDoubleClick(self, event) -> None:
|
||||
"""Handle double-click for polygon completion."""
|
||||
if self._areaSelectionHandler:
|
||||
self._areaSelectionHandler.on_double_click(event)
|
||||
|
||||
def _cropImage(self) -> None:
|
||||
"""Crop the image to the selected rectangular area. Starts selection mode if no selection exists."""
|
||||
if not self._areaSelectionHandler:
|
||||
@@ -440,6 +470,102 @@ class GUI:
|
||||
"""Display image properties in a dialog."""
|
||||
if self._imagePropertiesHandler:
|
||||
self._imagePropertiesHandler.show_properties()
|
||||
|
||||
def _applyGaussianFilter(self) -> None:
|
||||
"""Apply Gaussian blur filter to the image."""
|
||||
if self._currentImage is None:
|
||||
messagebox.showwarning("No Image", "Please load an image first.")
|
||||
return
|
||||
|
||||
# Get default parameters
|
||||
default_params = self._getDefaultParams(GaussianBlur())
|
||||
default_ksize = default_params.get("ksize", 5)
|
||||
|
||||
# Show parameter dialog
|
||||
if self._filterParamsHandler:
|
||||
params = self._filterParamsHandler.show_gaussian_filter_dialog(default_ksize)
|
||||
if params is None: # User cancelled
|
||||
return
|
||||
else:
|
||||
params = default_params
|
||||
|
||||
manipulation = GaussianBlur()
|
||||
self._applyManipulation(manipulation, params)
|
||||
|
||||
def _applySobelFilter(self) -> None:
|
||||
"""Apply Sobel edge detection filter to the image."""
|
||||
if self._currentImage is None:
|
||||
messagebox.showwarning("No Image", "Please load an image first.")
|
||||
return
|
||||
|
||||
# Get default parameters
|
||||
default_params = self._getDefaultParams(SobelEdge())
|
||||
default_dx = default_params.get("dx", 1)
|
||||
default_dy = default_params.get("dy", 0)
|
||||
default_ksize = default_params.get("ksize", 3)
|
||||
|
||||
# Show parameter dialog
|
||||
if self._filterParamsHandler:
|
||||
params = self._filterParamsHandler.show_sobel_filter_dialog(default_dx, default_dy, default_ksize)
|
||||
if params is None: # User cancelled
|
||||
return
|
||||
else:
|
||||
params = default_params
|
||||
|
||||
manipulation = SobelEdge()
|
||||
self._applyManipulation(manipulation, params)
|
||||
|
||||
def _applyBinaryFilter(self) -> None:
|
||||
"""Apply binary threshold filter to the image."""
|
||||
if self._currentImage is None:
|
||||
messagebox.showwarning("No Image", "Please load an image first.")
|
||||
return
|
||||
|
||||
# Get default parameters
|
||||
default_params = self._getDefaultParams(BinaryThreshold())
|
||||
default_thresh = default_params.get("thresh", 127)
|
||||
|
||||
# Show parameter dialog
|
||||
if self._filterParamsHandler:
|
||||
params = self._filterParamsHandler.show_binary_filter_dialog(default_thresh)
|
||||
if params is None: # User cancelled
|
||||
return
|
||||
else:
|
||||
params = default_params
|
||||
|
||||
manipulation = BinaryThreshold()
|
||||
self._applyManipulation(manipulation, params)
|
||||
|
||||
def _applyHistogramThreshold(self) -> None:
|
||||
"""Apply histogram-based thresholding (Otsu's method) to the image."""
|
||||
if self._currentImage is None:
|
||||
messagebox.showwarning("No Image", "Please load an image first.")
|
||||
return
|
||||
|
||||
# Histogram thresholding uses Otsu's method which doesn't need parameters
|
||||
manipulation = HistogramThreshold()
|
||||
params = {} # No parameters needed
|
||||
self._applyManipulation(manipulation, params)
|
||||
|
||||
def _zoomIn(self) -> None:
|
||||
"""Zoom in the image by a factor of 1.2."""
|
||||
if self._currentImage is None:
|
||||
messagebox.showwarning("No Image", "Please load an image first.")
|
||||
return
|
||||
|
||||
manipulation = ZoomImage()
|
||||
params = {"scale": 1.2}
|
||||
self._applyManipulation(manipulation, params)
|
||||
|
||||
def _zoomOut(self) -> None:
|
||||
"""Zoom out the image by a factor of 0.8."""
|
||||
if self._currentImage is None:
|
||||
messagebox.showwarning("No Image", "Please load an image first.")
|
||||
return
|
||||
|
||||
manipulation = ZoomImage()
|
||||
params = {"scale": 0.8}
|
||||
self._applyManipulation(manipulation, params)
|
||||
|
||||
def _openCamera(self):
|
||||
camera = CameraWindow()
|
||||
|
||||
Reference in New Issue
Block a user