Compare commits
5 Commits
37c322de75
...
382bccaedd
| Author | SHA1 | Date |
|---|---|---|
|
|
382bccaedd | |
|
|
e9db16be46 | |
|
|
e3013bd305 | |
|
|
1024d84bbc | |
|
|
1247269bb7 |
|
|
@ -12,6 +12,7 @@ class ColorPickerMixin:
|
||||||
|
|
||||||
ref_hue: float | None
|
ref_hue: float | None
|
||||||
hue_span: float = 45.0 # degrees around the picked hue
|
hue_span: float = 45.0 # degrees around the picked hue
|
||||||
|
selected_colour: tuple[int, int, int] | None = None
|
||||||
|
|
||||||
def choose_color(self):
|
def choose_color(self):
|
||||||
rgb, hex_colour = colorchooser.askcolor(title="Farbe wählen")
|
rgb, hex_colour = colorchooser.askcolor(title="Farbe wählen")
|
||||||
|
|
@ -23,6 +24,7 @@ class ColorPickerMixin:
|
||||||
self.status.config(
|
self.status.config(
|
||||||
text=f"Farbe gewählt: {label} — Hue {hue_deg:.1f}°, S {sat_pct:.0f}%, V {val_pct:.0f}%"
|
text=f"Farbe gewählt: {label} — Hue {hue_deg:.1f}°, S {sat_pct:.0f}%, V {val_pct:.0f}%"
|
||||||
)
|
)
|
||||||
|
self._update_selected_colour(r, g, b)
|
||||||
|
|
||||||
def apply_sample_colour(self, hex_colour: str, name: str | None = None) -> None:
|
def apply_sample_colour(self, hex_colour: str, name: str | None = None) -> None:
|
||||||
"""Apply a predefined colour preset."""
|
"""Apply a predefined colour preset."""
|
||||||
|
|
@ -39,6 +41,7 @@ class ColorPickerMixin:
|
||||||
f"Hue {hue_deg:.1f}°, S {sat_pct:.0f}%, V {val_pct:.0f}%"
|
f"Hue {hue_deg:.1f}°, S {sat_pct:.0f}%, V {val_pct:.0f}%"
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
self._update_selected_colour(*rgb)
|
||||||
|
|
||||||
def enable_pick_mode(self):
|
def enable_pick_mode(self):
|
||||||
if self.preview_img is None:
|
if self.preview_img is None:
|
||||||
|
|
@ -67,6 +70,7 @@ class ColorPickerMixin:
|
||||||
self.status.config(
|
self.status.config(
|
||||||
text=f"Farbe vom Bild gewählt: Hue {hue_deg:.1f}°, S {sat_pct:.0f}%, V {val_pct:.0f}%"
|
text=f"Farbe vom Bild gewählt: Hue {hue_deg:.1f}°, S {sat_pct:.0f}%, V {val_pct:.0f}%"
|
||||||
)
|
)
|
||||||
|
self._update_selected_colour(r, g, b)
|
||||||
|
|
||||||
def _apply_rgb_selection(self, r: int, g: int, b: int) -> tuple[float, float, float]:
|
def _apply_rgb_selection(self, r: int, g: int, b: int) -> tuple[float, float, float]:
|
||||||
"""Update slider ranges based on an RGB colour and return HSV summary."""
|
"""Update slider ranges based on an RGB colour and return HSV summary."""
|
||||||
|
|
@ -77,6 +81,15 @@ class ColorPickerMixin:
|
||||||
self.update_preview()
|
self.update_preview()
|
||||||
return hue_deg, s * 100.0, v * 100.0
|
return hue_deg, s * 100.0, v * 100.0
|
||||||
|
|
||||||
|
def _update_selected_colour(self, r: int, g: int, b: int) -> None:
|
||||||
|
self.selected_colour = (r, g, b)
|
||||||
|
if hasattr(self, "current_colour_sw"):
|
||||||
|
hex_colour = f"#{r:02x}{g:02x}{b:02x}"
|
||||||
|
try:
|
||||||
|
self.current_colour_sw.configure(background=hex_colour)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
def _set_slider_targets(self, hue_deg: float, saturation: float, value: float) -> None:
|
def _set_slider_targets(self, hue_deg: float, saturation: float, value: float) -> None:
|
||||||
span = getattr(self, "hue_span", 45.0)
|
span = getattr(self, "hue_span", 45.0)
|
||||||
self.hue_min.set((hue_deg - span) % 360)
|
self.hue_min.set((hue_deg - span) % 360)
|
||||||
|
|
|
||||||
|
|
@ -36,9 +36,11 @@ class ThemeMixin:
|
||||||
if self.theme == "dark":
|
if self.theme == "dark":
|
||||||
bg, fg = "#0f0f10", "#f1f1f1"
|
bg, fg = "#0f0f10", "#f1f1f1"
|
||||||
status_fg = "#f5f5f5"
|
status_fg = "#f5f5f5"
|
||||||
|
highlight_fg = "#f2c744"
|
||||||
else:
|
else:
|
||||||
bg, fg = "#ededf2", "#202020"
|
bg, fg = "#ededf2", "#202020"
|
||||||
status_fg = "#1c1c1c"
|
status_fg = "#1c1c1c"
|
||||||
|
highlight_fg = "#c56217"
|
||||||
self.root.configure(bg=bg) # type: ignore[attr-defined]
|
self.root.configure(bg=bg) # type: ignore[attr-defined]
|
||||||
|
|
||||||
s = self.style
|
s = self.style
|
||||||
|
|
@ -61,6 +63,10 @@ class ThemeMixin:
|
||||||
if callable(status_refresher) and hasattr(self, "status"):
|
if callable(status_refresher) and hasattr(self, "status"):
|
||||||
status_refresher(status_fg)
|
status_refresher(status_fg)
|
||||||
|
|
||||||
|
accent_refresher = getattr(self, "_refresh_accent_labels", None)
|
||||||
|
if callable(accent_refresher) and hasattr(self, "filename_label"):
|
||||||
|
accent_refresher(highlight_fg)
|
||||||
|
|
||||||
def detect_system_theme(self) -> str:
|
def detect_system_theme(self) -> str:
|
||||||
"""Best-effort detection of the OS theme preference."""
|
"""Best-effort detection of the OS theme preference."""
|
||||||
try:
|
try:
|
||||||
|
|
|
||||||
|
|
@ -21,8 +21,8 @@ class UIBuilderMixin:
|
||||||
("🎨", "Farbe wählen", self.choose_color),
|
("🎨", "Farbe wählen", self.choose_color),
|
||||||
("🖱", "Farbe aus Bild klicken", self.enable_pick_mode),
|
("🖱", "Farbe aus Bild klicken", self.enable_pick_mode),
|
||||||
("💾", "Overlay speichern", self.save_overlay),
|
("💾", "Overlay speichern", self.save_overlay),
|
||||||
("🧹", "Excludes löschen", self.clear_excludes),
|
("🧹", "Ausschlüsse löschen", self.clear_excludes),
|
||||||
("↩", "Letztes Exclude entfernen", self.undo_exclude),
|
("↩", "Letzten Ausschluss entfernen", self.undo_exclude),
|
||||||
("🔄", "Slider zurücksetzen", self.reset_sliders),
|
("🔄", "Slider zurücksetzen", self.reset_sliders),
|
||||||
("🌓", "Theme umschalten", self.toggle_theme),
|
("🌓", "Theme umschalten", self.toggle_theme),
|
||||||
]
|
]
|
||||||
|
|
@ -47,9 +47,20 @@ class UIBuilderMixin:
|
||||||
self.status_default_text = self.status.cget("text")
|
self.status_default_text = self.status.cget("text")
|
||||||
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=(6, 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))
|
self.current_colour_sw = tk.Canvas(
|
||||||
|
palette_frame,
|
||||||
|
width=24,
|
||||||
|
height=24,
|
||||||
|
highlightthickness=0,
|
||||||
|
background="#f2c744",
|
||||||
|
bd=0,
|
||||||
|
)
|
||||||
|
self.current_colour_sw.pack(side=tk.LEFT, padx=(0, 8), pady=2)
|
||||||
|
self.current_colour_label = ttk.Label(palette_frame, text="Aktuelle Farbe")
|
||||||
|
self.current_colour_label.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)
|
||||||
for name, hex_code in self._preset_colours():
|
for name, hex_code in self._preset_colours():
|
||||||
|
|
@ -98,26 +109,24 @@ class UIBuilderMixin:
|
||||||
|
|
||||||
info_frame = ttk.Frame(self.root)
|
info_frame = ttk.Frame(self.root)
|
||||||
info_frame.pack(fill=tk.X, padx=12, pady=(0, 12))
|
info_frame.pack(fill=tk.X, padx=12, pady=(0, 12))
|
||||||
self.filename_label = ttk.Label(
|
self.filename_label = ttk.Label(
|
||||||
info_frame,
|
info_frame,
|
||||||
text="—",
|
text="—",
|
||||||
foreground="#f2c744",
|
font=("Segoe UI", 10, "bold"),
|
||||||
font=("Segoe UI", 10, "bold"),
|
anchor="center",
|
||||||
anchor="center",
|
justify="center",
|
||||||
justify="center",
|
)
|
||||||
)
|
self.filename_label.pack(anchor="center")
|
||||||
self.filename_label.pack(anchor="center")
|
|
||||||
self._attach_copy_menu(self.filename_label)
|
self._attach_copy_menu(self.filename_label)
|
||||||
|
|
||||||
self.ratio_label = ttk.Label(
|
self.ratio_label = ttk.Label(
|
||||||
info_frame,
|
info_frame,
|
||||||
text="Treffer (mit Excludes): —",
|
text="Markierungen (mit Ausschlüssen): —",
|
||||||
foreground="#f2c744",
|
font=("Segoe UI", 10, "bold"),
|
||||||
font=("Segoe UI", 10, "bold"),
|
anchor="center",
|
||||||
anchor="center",
|
justify="center",
|
||||||
justify="center",
|
)
|
||||||
)
|
self.ratio_label.pack(anchor="center", pady=(4, 0))
|
||||||
self.ratio_label.pack(anchor="center", pady=(4, 0))
|
|
||||||
self._attach_copy_menu(self.ratio_label)
|
self._attach_copy_menu(self.ratio_label)
|
||||||
|
|
||||||
self.root.bind("<Escape>", self.disable_pick_mode)
|
self.root.bind("<Escape>", self.disable_pick_mode)
|
||||||
|
|
@ -495,9 +504,16 @@ class UIBuilderMixin:
|
||||||
activeforeground=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
|
||||||
|
|
||||||
|
def _refresh_accent_labels(self, colour: str) -> None:
|
||||||
|
try:
|
||||||
|
self.filename_label.configure(foreground=colour)
|
||||||
|
self.ratio_label.configure(foreground=colour)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
def _init_copy_menu(self):
|
def _init_copy_menu(self):
|
||||||
self._copy_target = None
|
self._copy_target = None
|
||||||
|
|
|
||||||
|
|
@ -64,16 +64,20 @@ class ImageProcessingMixin:
|
||||||
def show_next_image(self, event=None) -> None:
|
def show_next_image(self, event=None) -> None:
|
||||||
if not getattr(self, "image_paths", None):
|
if not getattr(self, "image_paths", None):
|
||||||
return
|
return
|
||||||
next_index = getattr(self, "current_image_index", -1) + 1
|
if not self.image_paths:
|
||||||
if next_index < len(self.image_paths):
|
return
|
||||||
self._display_image_by_index(next_index)
|
current = getattr(self, "current_image_index", -1)
|
||||||
|
next_index = (current + 1) % len(self.image_paths)
|
||||||
|
self._display_image_by_index(next_index)
|
||||||
|
|
||||||
def show_previous_image(self, event=None) -> None:
|
def show_previous_image(self, event=None) -> None:
|
||||||
if not getattr(self, "image_paths", None):
|
if not getattr(self, "image_paths", None):
|
||||||
return
|
return
|
||||||
prev_index = getattr(self, "current_image_index", -1) - 1
|
if not self.image_paths:
|
||||||
if prev_index >= 0:
|
return
|
||||||
self._display_image_by_index(prev_index)
|
current = getattr(self, "current_image_index", -1)
|
||||||
|
prev_index = (current - 1) % len(self.image_paths)
|
||||||
|
self._display_image_by_index(prev_index)
|
||||||
|
|
||||||
def _set_image_collection(self, paths: Sequence[Path], start_index: int) -> None:
|
def _set_image_collection(self, paths: Sequence[Path], start_index: int) -> None:
|
||||||
self.image_paths = list(paths)
|
self.image_paths = list(paths)
|
||||||
|
|
@ -182,9 +186,9 @@ class ImageProcessingMixin:
|
||||||
excl_match = (matches_ex / total_ex * 100) if total_ex else 0.0
|
excl_match = (matches_ex / total_ex * 100) if total_ex else 0.0
|
||||||
self.ratio_label.config(
|
self.ratio_label.config(
|
||||||
text=(
|
text=(
|
||||||
f"Treffer (mit Excludes): {r_with:.2f}% | "
|
f"Markierungen (mit Ausschlüssen): {r_with:.2f}% | "
|
||||||
f"Treffer (ohne Excludes): {r_no:.2f}% | "
|
f"Markierungen (ohne Ausschlüsse): {r_no:.2f}% | "
|
||||||
f"Excluded: {excl_share:.2f}% vom Bild, davon {excl_match:.2f}% Treffer"
|
f"Ausgeschlossen: {excl_share:.2f}% der Pixel, davon {excl_match:.2f}% markiert"
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue