Compare commits
4 Commits
bed81e6d50
...
a49a2d18cf
| Author | SHA1 | Date |
|---|---|---|
|
|
a49a2d18cf | |
|
|
c7ac43dc37 | |
|
|
1588e8eb99 | |
|
|
9455fb9c4c |
12
README.md
12
README.md
|
|
@ -1,8 +1,6 @@
|
||||||
# ICRS (Interactive Color Range Analyzer)
|
<img src="app/assets/logo.png" alt="ICRA Logo" width="120"/>
|
||||||
|
|
||||||
<img src="app/assets/logo.png" alt="ICRS Logo" width="120"/>
|
**ICRA** (Interactive Color Range Analyzer) is a Tkinter-based desktop tool for highlighting customised colour ranges in images. Load a single photo or an entire folder, fine-tune hue/saturation/value sliders, and export overlays complete with quick statistics.
|
||||||
|
|
||||||
ICRS is a small Tkinter tool for analysing colour ranges in images. You load a picture, pick or click a reference colour, adjust hue/saturation/value sliders, and the app marks matching pixels while showing quick stats.
|
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
- Two synced previews (original + overlay)
|
- Two synced previews (original + overlay)
|
||||||
|
|
@ -19,12 +17,12 @@ ICRS is a small Tkinter tool for analysing colour ranges in images. You load a p
|
||||||
|
|
||||||
## Setup with uv (Windows PowerShell)
|
## Setup with uv (Windows PowerShell)
|
||||||
```powershell
|
```powershell
|
||||||
git clone https://git.lukasmahler.de/lm/ICRS.git
|
git clone https://git.lukasmahler.de/lm/ICRA.git
|
||||||
cd ICRS
|
cd ICRA
|
||||||
uv venv
|
uv venv
|
||||||
.\.venv\Scripts\Activate
|
.\.venv\Scripts\Activate
|
||||||
uv pip install .
|
uv pip install .
|
||||||
uv run icrs
|
uv run icra
|
||||||
```
|
```
|
||||||
The launcher copies Tcl/Tk resources into the virtualenv on first run, so no manual environment tweaks are needed. On macOS/Linux replace the activate step with `source .venv/bin/activate`.
|
The launcher copies Tcl/Tk resources into the virtualenv on first run, so no manual environment tweaks are needed. On macOS/Linux replace the activate step with `source .venv/bin/activate`.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
"""Application package."""
|
"""Application package."""
|
||||||
|
|
||||||
from .app import ICRSApp, start_app
|
from .app import ICRAApp, start_app
|
||||||
|
|
||||||
__all__ = ["ICRSApp", "start_app"]
|
__all__ = ["ICRAApp", "start_app"]
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ from .gui import ColorPickerMixin, ExclusionMixin, ThemeMixin, UIBuilderMixin
|
||||||
from .logic import DEFAULTS, ImageProcessingMixin, ResetMixin
|
from .logic import DEFAULTS, ImageProcessingMixin, ResetMixin
|
||||||
|
|
||||||
|
|
||||||
class ICRSApp(
|
class ICRAApp(
|
||||||
ThemeMixin,
|
ThemeMixin,
|
||||||
UIBuilderMixin,
|
UIBuilderMixin,
|
||||||
ImageProcessingMixin,
|
ImageProcessingMixin,
|
||||||
|
|
@ -20,7 +20,7 @@ class ICRSApp(
|
||||||
|
|
||||||
def __init__(self, root: tk.Tk):
|
def __init__(self, root: tk.Tk):
|
||||||
self.root = root
|
self.root = root
|
||||||
self.root.title("ICRS — Interactive Color Range Analyzer")
|
self.root.title("ICRA — Interactive Color Range Analyzer")
|
||||||
self._setup_window()
|
self._setup_window()
|
||||||
|
|
||||||
# Theme and styling
|
# Theme and styling
|
||||||
|
|
@ -71,8 +71,8 @@ class ICRSApp(
|
||||||
def start_app() -> None:
|
def start_app() -> None:
|
||||||
"""Entry point used by the CLI script."""
|
"""Entry point used by the CLI script."""
|
||||||
root = tk.Tk()
|
root = tk.Tk()
|
||||||
app = ICRSApp(root)
|
app = ICRAApp(root)
|
||||||
root.mainloop()
|
root.mainloop()
|
||||||
|
|
||||||
|
|
||||||
__all__ = ["ICRSApp", "start_app"]
|
__all__ = ["ICRAApp", "start_app"]
|
||||||
|
|
|
||||||
|
|
@ -53,6 +53,10 @@ class ThemeMixin:
|
||||||
if callable(button_refresher):
|
if callable(button_refresher):
|
||||||
button_refresher()
|
button_refresher()
|
||||||
|
|
||||||
|
nav_refresher = getattr(self, "_refresh_navigation_buttons_theme", None)
|
||||||
|
if callable(nav_refresher):
|
||||||
|
nav_refresher()
|
||||||
|
|
||||||
status_refresher = getattr(self, "_refresh_status_palette", None)
|
status_refresher = getattr(self, "_refresh_status_palette", None)
|
||||||
if callable(status_refresher) and hasattr(self, "status"):
|
if callable(status_refresher) and hasattr(self, "status"):
|
||||||
status_refresher(status_fg)
|
status_refresher(status_fg)
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ class UIBuilderMixin:
|
||||||
self._create_titlebar()
|
self._create_titlebar()
|
||||||
|
|
||||||
toolbar = ttk.Frame(self.root)
|
toolbar = ttk.Frame(self.root)
|
||||||
toolbar.pack(fill=tk.X, padx=12, pady=0)
|
toolbar.pack(fill=tk.X, padx=12, pady=(4, 2))
|
||||||
buttons = [
|
buttons = [
|
||||||
("📂 Bild laden", self.load_image),
|
("📂 Bild laden", self.load_image),
|
||||||
("📁 Ordner laden", self.load_folder),
|
("📁 Ordner laden", self.load_folder),
|
||||||
|
|
@ -27,6 +27,7 @@ class UIBuilderMixin:
|
||||||
("🌓 Theme umschalten", self.toggle_theme),
|
("🌓 Theme umschalten", self.toggle_theme),
|
||||||
]
|
]
|
||||||
self._toolbar_buttons: list[dict[str, object]] = []
|
self._toolbar_buttons: list[dict[str, object]] = []
|
||||||
|
self._nav_buttons: list[tk.Button] = []
|
||||||
|
|
||||||
buttons_frame = ttk.Frame(toolbar)
|
buttons_frame = ttk.Frame(toolbar)
|
||||||
buttons_frame.pack(side=tk.LEFT)
|
buttons_frame.pack(side=tk.LEFT)
|
||||||
|
|
@ -47,7 +48,7 @@ class UIBuilderMixin:
|
||||||
self._status_palette = {"fg": self.status.cget("foreground")}
|
self._status_palette = {"fg": self.status.cget("foreground")}
|
||||||
|
|
||||||
palette_frame = ttk.Frame(self.root)
|
palette_frame = ttk.Frame(self.root)
|
||||||
palette_frame.pack(fill=tk.X, padx=12, pady=(0, 8))
|
palette_frame.pack(fill=tk.X, padx=12, pady=(6, 8))
|
||||||
ttk.Label(palette_frame, text="Beispielfarben:").pack(side=tk.LEFT, padx=(0, 8))
|
ttk.Label(palette_frame, text="Beispielfarben:").pack(side=tk.LEFT, padx=(0, 8))
|
||||||
swatch_container = ttk.Frame(palette_frame)
|
swatch_container = ttk.Frame(palette_frame)
|
||||||
swatch_container.pack(side=tk.LEFT)
|
swatch_container.pack(side=tk.LEFT)
|
||||||
|
|
@ -326,9 +327,10 @@ class UIBuilderMixin:
|
||||||
return canvas.create_polygon(points, smooth=True, splinesteps=24, **kwargs)
|
return canvas.create_polygon(points, smooth=True, splinesteps=24, **kwargs)
|
||||||
|
|
||||||
def _create_navigation_button(self, container, symbol: str, command, *, column: int) -> None:
|
def _create_navigation_button(self, container, symbol: str, command, *, column: int) -> None:
|
||||||
bg = self.root.cget("bg") if hasattr(self.root, "cget") else "#f2f2f7"
|
palette = self._navigation_palette()
|
||||||
|
bg = palette["bg"]
|
||||||
|
fg = palette["fg"]
|
||||||
container.grid_rowconfigure(0, weight=1)
|
container.grid_rowconfigure(0, weight=1)
|
||||||
fg = "#f5f5f5" if getattr(self, "theme", "light") == "dark" else "#1f1f1f"
|
|
||||||
btn = tk.Button(
|
btn = tk.Button(
|
||||||
container,
|
container,
|
||||||
text=symbol,
|
text=symbol,
|
||||||
|
|
@ -345,6 +347,7 @@ class UIBuilderMixin:
|
||||||
width=2,
|
width=2,
|
||||||
)
|
)
|
||||||
btn.grid(row=0, column=column, sticky="ns", padx=6)
|
btn.grid(row=0, column=column, sticky="ns", padx=6)
|
||||||
|
self._nav_buttons.append(btn)
|
||||||
|
|
||||||
def _create_titlebar(self) -> None:
|
def _create_titlebar(self) -> None:
|
||||||
bar_bg = "#1f1f1f"
|
bar_bg = "#1f1f1f"
|
||||||
|
|
@ -371,7 +374,7 @@ class UIBuilderMixin:
|
||||||
|
|
||||||
title_label = tk.Label(
|
title_label = tk.Label(
|
||||||
title_bar,
|
title_bar,
|
||||||
text="ICRS — Interactive Color Range Analyzer",
|
text="ICRA — Interactive Color Range Analyzer",
|
||||||
bg=bar_bg,
|
bg=bar_bg,
|
||||||
fg="#f5f5f5",
|
fg="#f5f5f5",
|
||||||
font=("Segoe UI", 11, "bold"),
|
font=("Segoe UI", 11, "bold"),
|
||||||
|
|
@ -443,6 +446,12 @@ class UIBuilderMixin:
|
||||||
"text": "#1f1f1f",
|
"text": "#1f1f1f",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def _navigation_palette(self) -> dict[str, str]:
|
||||||
|
is_dark = getattr(self, "theme", "light") == "dark"
|
||||||
|
if is_dark:
|
||||||
|
return {"bg": "#26262a", "fg": "#f5f5f5"}
|
||||||
|
return {"bg": "#e8e8ee", "fg": "#1f1f1f"}
|
||||||
|
|
||||||
def _refresh_toolbar_buttons_theme(self) -> None:
|
def _refresh_toolbar_buttons_theme(self) -> None:
|
||||||
if not getattr(self, "_toolbar_buttons", None):
|
if not getattr(self, "_toolbar_buttons", None):
|
||||||
return
|
return
|
||||||
|
|
@ -457,6 +466,18 @@ class UIBuilderMixin:
|
||||||
canvas.itemconfigure(rect_id, fill=palette["normal"], outline=palette["outline"])
|
canvas.itemconfigure(rect_id, fill=palette["normal"], outline=palette["outline"])
|
||||||
canvas.itemconfigure(text_id, fill=palette["text"])
|
canvas.itemconfigure(text_id, fill=palette["text"])
|
||||||
|
|
||||||
|
def _refresh_navigation_buttons_theme(self) -> None:
|
||||||
|
if not getattr(self, "_nav_buttons", None):
|
||||||
|
return
|
||||||
|
palette = self._navigation_palette()
|
||||||
|
for btn in self._nav_buttons:
|
||||||
|
btn.configure(
|
||||||
|
background=palette["bg"],
|
||||||
|
activebackground=palette["bg"],
|
||||||
|
fg=palette["fg"],
|
||||||
|
activeforeground=palette["fg"],
|
||||||
|
)
|
||||||
|
|
||||||
def _refresh_status_palette(self, fg: str) -> None:
|
def _refresh_status_palette(self, fg: str) -> None:
|
||||||
self.status.configure(foreground=fg)
|
self.status.configure(foreground=fg)
|
||||||
self._status_palette["fg"] = fg
|
self._status_palette["fg"] = fg
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
"""Launcher ensuring Tcl/Tk resources are available before starting ICRS."""
|
"""Launcher ensuring Tcl/Tk resources are available before starting ICRA."""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
[project]
|
[project]
|
||||||
name = "icrs"
|
name = "icra"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
description = "Interactive Color Range Analyzer (ICRS) for Tkinter"
|
description = "Interactive Color Range Analyzer (ICRA) for Tkinter"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
authors = [{ name = "ICRS contributors" }]
|
authors = [{ name = "ICRA contributors" }]
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
requires-python = ">=3.10"
|
requires-python = ">=3.10"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
|
@ -11,7 +11,7 @@ dependencies = [
|
||||||
]
|
]
|
||||||
|
|
||||||
[project.scripts]
|
[project.scripts]
|
||||||
icrs = "app.launcher:main"
|
icra = "app.launcher:main"
|
||||||
|
|
||||||
[tool.uv]
|
[tool.uv]
|
||||||
package = true
|
package = true
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue