diff --git a/.gitignore b/.gitignore
index abd5980..98da2e3 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,179 @@
cache/*
cache/**
+
+# Created by https://www.toptal.com/developers/gitignore/api/python
+# Edit at https://www.toptal.com/developers/gitignore?templates=python
+
+### Python ###
+# Byte-compiled / optimized / DLL files
+__pycache__/
+*.py[cod]
+*$py.class
+
+# C extensions
+*.so
+
+# Distribution / packaging
+.Python
+build/
+develop-eggs/
+dist/
+downloads/
+eggs/
+.eggs/
+lib/
+lib64/
+parts/
+sdist/
+var/
+wheels/
+share/python-wheels/
+*.egg-info/
+.installed.cfg
+*.egg
+MANIFEST
+
+# PyInstaller
+# Usually these files are written by a python script from a template
+# before PyInstaller builds the exe, so as to inject date/other infos into it.
+*.manifest
+*.spec
+
+# Installer logs
+pip-log.txt
+pip-delete-this-directory.txt
+
+# Unit test / coverage reports
+htmlcov/
+.tox/
+.nox/
+.coverage
+.coverage.*
+.cache
+nosetests.xml
+coverage.xml
+*.cover
+*.py,cover
+.hypothesis/
+.pytest_cache/
+cover/
+
+# Translations
+*.mo
+*.pot
+
+# Django stuff:
+*.log
+local_settings.py
+db.sqlite3
+db.sqlite3-journal
+
+# Flask stuff:
+instance/
+.webassets-cache
+
+# Scrapy stuff:
+.scrapy
+
+# Sphinx documentation
+docs/_build/
+
+# PyBuilder
+.pybuilder/
+target/
+
+# Jupyter Notebook
+.ipynb_checkpoints
+
+# IPython
+profile_default/
+ipython_config.py
+
+# pyenv
+# For a library or package, you might want to ignore these files since the code is
+# intended to run in multiple environments; otherwise, check them in:
+# .python-version
+
+# pipenv
+# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
+# However, in case of collaboration, if having platform-specific dependencies or dependencies
+# having no cross-platform support, pipenv may install dependencies that don't work, or not
+# install all needed dependencies.
+#Pipfile.lock
+
+# poetry
+# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
+# This is especially recommended for binary packages to ensure reproducibility, and is more
+# commonly ignored for libraries.
+# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
+#poetry.lock
+
+# pdm
+# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
+#pdm.lock
+# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
+# in version control.
+# https://pdm.fming.dev/#use-with-ide
+.pdm.toml
+
+# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
+__pypackages__/
+
+# Celery stuff
+celerybeat-schedule
+celerybeat.pid
+
+# SageMath parsed files
+*.sage.py
+
+# Environments
+.env
+.venv
+env/
+venv/
+ENV/
+env.bak/
+venv.bak/
+
+# Spyder project settings
+.spyderproject
+.spyproject
+
+# Rope project settings
+.ropeproject
+
+# mkdocs documentation
+/site
+
+# mypy
+.mypy_cache/
+.dmypy.json
+dmypy.json
+
+# Pyre type checker
+.pyre/
+
+# pytype static type analyzer
+.pytype/
+
+# Cython debug symbols
+cython_debug/
+
+# PyCharm
+# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
+# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
+# and can be added to the global gitignore or merged into this file. For a more nuclear
+# option (not recommended) you can uncomment the following to ignore the entire idea folder.
+#.idea/
+
+### Python Patch ###
+# Poetry local configuration file - https://python-poetry.org/docs/configuration/#local-configuration
+poetry.toml
+
+# ruff
+.ruff_cache/
+
+# LSP config files
+pyrightconfig.json
+
+# End of https://www.toptal.com/developers/gitignore/api/python
diff --git a/.idea/.gitignore b/.idea/.gitignore
new file mode 100644
index 0000000..13566b8
--- /dev/null
+++ b/.idea/.gitignore
@@ -0,0 +1,8 @@
+# Default ignored files
+/shelf/
+/workspace.xml
+# Editor-based HTTP Client requests
+/httpRequests/
+# Datasource local storage ignored files
+/dataSources/
+/dataSources.local.xml
diff --git a/.idea/runConfigurations/Run_project.xml b/.idea/runConfigurations/Run_project.xml
new file mode 100644
index 0000000..7b2c8e5
--- /dev/null
+++ b/.idea/runConfigurations/Run_project.xml
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 0000000..94a25f7
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/workspace.xml b/.idea/workspace.xml
index c26b869..8db849e 100644
--- a/.idea/workspace.xml
+++ b/.idea/workspace.xml
@@ -1,7 +1,13 @@
+
+
+
-
+
+
+
+
@@ -14,9 +20,12 @@
-
+
+
+
+ {
+ "associatedIndex": 7
+}
@@ -25,8 +34,20 @@
-
-
+
+
@@ -47,10 +68,17 @@
1757587375498
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/README.md b/README.md
index 32ca216..59e6eaf 100644
--- a/README.md
+++ b/README.md
@@ -6,7 +6,7 @@ pillow
numpy
## run
-python main.py
+python src/Main.py
## menu items
* file
diff --git a/docs/UML/Class Diagram.puml b/docs/UML/Class Diagram.puml
new file mode 100644
index 0000000..ccce635
--- /dev/null
+++ b/docs/UML/Class Diagram.puml
@@ -0,0 +1,60 @@
+@startuml
+
+class GUI{
+ This is a singleton
+ ---
+ - ImageContainer _currentImage
+ - ImageManipulation[] _imageManipulations
+ + none initialise()
+}
+
+
+class ImageContainer{
+ - imagedata _imageData
+ + none loadImage(path: str)
+ + imagedata getImage()
+ + pair getDimensions()
+}
+
+abstract class ImageManipulation{
+ +none manipulateImage(ImageContainer, Parameters)
+ +str[] getParameters()
+ +str getManipulationName()
+}
+
+class CropImage{
+- str _name
+}
+
+class GreyScaleImage{
+- str _name
+}
+
+class SmoothImage{
+- str _name
+}
+
+class RotateImage{
+- str _name
+}
+
+class HSVImage{
+- str _name
+}
+
+class HueShiftImage{
+- str _name
+}
+
+GUI ..> ImageContainer
+GUI ..> ImageManipulation
+
+CropImage ..|> ImageManipulation
+GreyScaleImage ..|> ImageManipulation
+SmoothImage ..|> ImageManipulation
+RotateImage ..|> ImageManipulation
+HSVImage ..|> ImageManipulation
+HueShiftImage ..|> ImageManipulation
+
+
+@enduml
\ No newline at end of file
diff --git a/docs/UML/Component Diagram.puml b/docs/UML/Component Diagram.puml
new file mode 100644
index 0000000..2dfe922
--- /dev/null
+++ b/docs/UML/Component Diagram.puml
@@ -0,0 +1,7 @@
+@startuml
+[GUI]
+[Image Processing]
+
+[GUI] ..> [Image Processing]
+
+@enduml
\ No newline at end of file
diff --git a/requirements.txt b/requirements.txt
new file mode 100644
index 0000000..0e0efb4
--- /dev/null
+++ b/requirements.txt
@@ -0,0 +1,2 @@
+pillow~=11.3.0
+numpy~=2.3.1
diff --git a/src/GUI.py b/src/GUI.py
new file mode 100644
index 0000000..20718a4
--- /dev/null
+++ b/src/GUI.py
@@ -0,0 +1,75 @@
+import tkinter as tk
+from tkinter import filedialog
+from PIL import ImageTk
+from ImageContainer import ImageContainer
+from ImageManipulation.ManipulationList import *
+
+
+class GUI:
+ """The GUI class responsible for the main application GUI.
+
+ @warning This class is a singleton.
+ """
+ _instance = None
+
+ # If the GUI has been initialised.
+ _isInitialised = False
+
+ _currentImage = None
+
+ def __new__(cls):
+ if cls._instance is None:
+ cls._instance = super(GUI, cls).__new__(cls)
+ return cls._instance
+
+ def initialise(self):
+ """Initialise the GUI."""
+
+ if self._isInitialised:
+ return
+ else:
+ self._isInitialised = True
+
+ # Main window
+ root = tk.Tk()
+ root.title("Image Viewer")
+ root.geometry("800x600")
+ root.config(bg="white")
+ icon = tk.PhotoImage(file='icon.png')
+ root.tk.call('wm', 'iconphoto', root._w, icon)
+ root.minsize(800, 600)
+
+ # Menus
+ menu = tk.Menu(root)
+ root.config(menu=menu)
+ file_menu = tk.Menu(menu, tearoff=0)
+ menu.add_cascade(label="File", menu=file_menu)
+ file_menu.add_command(label="Open Image", command=lambda: self._openImage())
+ file_menu.add_command(label="Save Image", command=lambda: _save_image(OPENED_IMAGE))
+ file_menu.add_command(label="Exit", command=root.quit)
+
+
+ test_menu = tk.Menu(menu, tearoff=0)
+ menu.add_cascade(label="Filters", menu=test_menu)
+ for manipulation in GetImageManipulationList():
+ test_menu.add_command(label=manipulation.getManipulationName(), command=lambda: manipulation.manipulateImage())
+
+ # Frame to hold image
+ imgframe = tk.Frame(root, width=500, height=500, bg="lightgray", relief="sunken", bd=2)
+ imgframe.pack(side="top", pady=10)
+
+ # Label to display image
+ image_label = tk.Label(imgframe, width=500, height=500, bg="white")
+ image_label.pack(expand=True)
+
+ root.mainloop()
+
+ def _openImage(self) -> None:
+ file_path = filedialog.askopenfilename(
+ filetypes=[("Image files", "*.jpg *.jpeg *.png *.gif *.bmp")]
+ )
+ if not file_path:
+ return
+
+ self._currentImage = ImageContainer()
+ self._currentImage.loadImage(file_path)
\ No newline at end of file
diff --git a/src/ImageContainer.py b/src/ImageContainer.py
new file mode 100644
index 0000000..4f948eb
--- /dev/null
+++ b/src/ImageContainer.py
@@ -0,0 +1,25 @@
+import cv2
+from PIL import Image, ImageTk
+
+class ImageContainer:
+ _imageData = None
+ _path = None
+
+ def loadImage(self, path: str) -> None:
+ """ Load image file from the path.
+
+ :param path: Path to the image file.
+ """
+ imgcv2 = cv2.imread(path)
+ height, width, channels = imgcv2.shape
+
+ # Open and resize image (optional)
+ _imageData = Image.open(path)
+ _imageData = _imageData.resize((height, width), Image.LANCZOS) # Resize to fit frame
+
+ _path = path
+
+ print("Opened image:", path)
+
+ def getDimensions(self) -> tuple[int,int]:
+ return self._imageData.shape
\ No newline at end of file
diff --git a/src/ImageManipulation/CropImage.py b/src/ImageManipulation/CropImage.py
new file mode 100644
index 0000000..2f63014
--- /dev/null
+++ b/src/ImageManipulation/CropImage.py
@@ -0,0 +1,39 @@
+from .ImageManipulation import ImageManipulation
+from ..ImageContainer import ImageContainer
+from typing import Any, List
+
+
+class CropImage(ImageManipulation):
+ """Concrete implementation for cropping images."""
+
+ def __init__(self):
+ # You can initialize any instance variables here
+ self.manipulation_name = "Crop"
+
+ def manipulateImage(self, image: ImageContainer, parameters: Any) -> None:
+ """
+ Manipulate the given image using the provided parameters.
+
+ Args:
+ image: The image to manipulate
+ parameters: Parameters for the manipulation (e.g., crop coordinates)
+ """
+ pass
+
+ def getParameters(self) -> List[str]:
+ """
+ Get the list of parameters required for cropping.
+
+ Returns:
+ List of parameter names
+ """
+ return ["width", "height"]
+
+ def getManipulationName(self) -> str:
+ """
+ Get the name of this manipulation operation.
+
+ Returns:
+ The name of the manipulation
+ """
+ return "Crop Image"
\ No newline at end of file
diff --git a/src/ImageManipulation/ImageManipulation.py b/src/ImageManipulation/ImageManipulation.py
new file mode 100644
index 0000000..907796b
--- /dev/null
+++ b/src/ImageManipulation/ImageManipulation.py
@@ -0,0 +1,42 @@
+from abc import ABC, abstractmethod
+from typing import Any
+
+import sys
+
+sys.path.append('..')
+from ..ImageContainer import ImageContainer
+
+
+class ImageManipulation(ABC):
+ """Abstract base class for image manipulation operations."""
+
+ @abstractmethod
+ def manipulateImage(self, image: ImageContainer, parameters: Any) -> None:
+ """
+ Manipulate the given image using the provided parameters.
+
+ Args:
+ image: The image to manipulate
+ parameters: Parameters for the manipulation
+ """
+ pass
+
+ @abstractmethod
+ def getParameters(self) -> list[str]:
+ """
+ Get the list of parameters required for this manipulation.
+
+ Returns:
+ List of parameter names
+ """
+ pass
+
+ @abstractmethod
+ def getManipulationName(self) -> str:
+ """
+ Get the name of this manipulation operation.
+
+ Returns:
+ The name of the manipulation
+ """
+ pass
diff --git a/src/ImageManipulation/ManipulationList.py b/src/ImageManipulation/ManipulationList.py
new file mode 100644
index 0000000..aeff678
--- /dev/null
+++ b/src/ImageManipulation/ManipulationList.py
@@ -0,0 +1,4 @@
+from src.ImageManipulation.CropImage import *
+
+def GetImageManipulationList() -> list:
+ return [CropImage()]
\ No newline at end of file
diff --git a/src/Main.py b/src/Main.py
new file mode 100644
index 0000000..d79ee9f
--- /dev/null
+++ b/src/Main.py
@@ -0,0 +1,6 @@
+#!/usr/bin/python
+from GUI import GUI
+
+if __name__ == "__main__":
+ gui = GUI()
+ gui.initialise()
\ No newline at end of file