Compare commits

...

4 Commits

7 changed files with 46 additions and 23 deletions

View File

@ -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`.

View File

@ -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"]

View File

@ -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"]

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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