Compare commits

...

2 Commits

Author SHA1 Message Date
lm 1d0acba3c3 Add exclusion persistence option
Introduce a config flag to reset exclusions when images change (default off), keep shapes when navigating, and theme preview accents accordingly.
2025-10-17 17:29:21 +02:00
lm 987c2e9e4a Use themed preview accents
Apply the theme-specific accent colour to exclusion previews so new shapes match the final outline in both light and dark modes.
2025-10-17 17:25:27 +02:00
6 changed files with 54 additions and 5 deletions

View File

@ -6,7 +6,7 @@ import tkinter as tk
from .gui import ColorPickerMixin, ExclusionMixin, ThemeMixin, UIBuilderMixin from .gui import ColorPickerMixin, ExclusionMixin, ThemeMixin, UIBuilderMixin
from .i18n import I18nMixin from .i18n import I18nMixin
from .logic import DEFAULTS, LANGUAGE, ImageProcessingMixin, ResetMixin from .logic import DEFAULTS, LANGUAGE, RESET_EXCLUSIONS_ON_IMAGE_CHANGE, ImageProcessingMixin, ResetMixin
class ICRAApp( class ICRAApp(
@ -49,6 +49,7 @@ class ICRAApp(
self._rubber_id = None self._rubber_id = None
self._stroke_preview_id = None self._stroke_preview_id = None
self.exclude_mode = "rect" self.exclude_mode = "rect"
self.reset_exclusions_on_switch = RESET_EXCLUSIONS_ON_IMAGE_CHANGE
self._exclude_mask = None self._exclude_mask = None
self._exclude_mask_dirty = True self._exclude_mask_dirty = True
self._exclude_mask_px = None self._exclude_mask_px = None

View File

@ -20,12 +20,13 @@ class ExclusionMixin:
self.canvas_orig.delete(preview_id) self.canvas_orig.delete(preview_id)
except Exception: except Exception:
pass pass
accent = self._exclusion_preview_colour()
self._stroke_preview_id = self.canvas_orig.create_line( self._stroke_preview_id = self.canvas_orig.create_line(
x, x,
y, y,
x, x,
y, y,
fill="yellow", fill=accent,
width=2, width=2,
smooth=True, smooth=True,
capstyle="round", capstyle="round",
@ -39,7 +40,8 @@ class ExclusionMixin:
self.canvas_orig.delete(self._rubber_id) self.canvas_orig.delete(self._rubber_id)
except Exception: except Exception:
pass pass
self._rubber_id = self.canvas_orig.create_rectangle(x, y, x, y, outline="yellow", width=2) accent = self._exclusion_preview_colour()
self._rubber_id = self.canvas_orig.create_rectangle(x, y, x, y, outline=accent, width=2)
def _exclude_drag(self, event): def _exclude_drag(self, event):
mode = getattr(self, "exclude_mode", "rect") mode = getattr(self, "exclude_mode", "rect")
@ -168,6 +170,7 @@ class ExclusionMixin:
except Exception: except Exception:
pass pass
self._stroke_preview_id = None self._stroke_preview_id = None
self._rubber_id = None
message_key = "status.free_draw_enabled" if next_mode == "free" else "status.free_draw_disabled" message_key = "status.free_draw_enabled" if next_mode == "free" else "status.free_draw_disabled"
if hasattr(self, "status"): if hasattr(self, "status"):
try: try:
@ -186,6 +189,10 @@ class ExclusionMixin:
compressed.append(point) compressed.append(point)
return compressed return compressed
def _exclusion_preview_colour(self) -> str:
is_dark = getattr(self, "theme", "light") == "dark"
return "#ffd700" if is_dark else "#c56217"
@staticmethod @staticmethod
def _close_polygon(points: list[tuple[int, int]]) -> list[tuple[int, int]]: def _close_polygon(points: list[tuple[int, int]]) -> list[tuple[int, int]]:
"""Ensure the polygon is closed by repeating the start if necessary.""" """Ensure the polygon is closed by repeating the start if necessary."""

View File

@ -1,6 +1,14 @@
"""Logic utilities and mixins for processing and configuration.""" """Logic utilities and mixins for processing and configuration."""
from .constants import BASE_DIR, DEFAULTS, IMAGES_DIR, LANGUAGE, PREVIEW_MAX_SIZE, SUPPORTED_IMAGE_EXTENSIONS from .constants import (
BASE_DIR,
DEFAULTS,
IMAGES_DIR,
LANGUAGE,
PREVIEW_MAX_SIZE,
RESET_EXCLUSIONS_ON_IMAGE_CHANGE,
SUPPORTED_IMAGE_EXTENSIONS,
)
from .image_processing import ImageProcessingMixin from .image_processing import ImageProcessingMixin
from .reset import ResetMixin from .reset import ResetMixin
@ -10,6 +18,7 @@ __all__ = [
"IMAGES_DIR", "IMAGES_DIR",
"LANGUAGE", "LANGUAGE",
"PREVIEW_MAX_SIZE", "PREVIEW_MAX_SIZE",
"RESET_EXCLUSIONS_ON_IMAGE_CHANGE",
"SUPPORTED_IMAGE_EXTENSIONS", "SUPPORTED_IMAGE_EXTENSIONS",
"ImageProcessingMixin", "ImageProcessingMixin",
"ResetMixin", "ResetMixin",

View File

@ -92,7 +92,25 @@ def _extract_language(data: dict[str, Any]) -> str:
return sorted(supported)[0] return sorted(supported)[0]
_CONFIG_DATA = _load_config_data()
_OPTION_DEFAULTS = {"reset_exclusions_on_image_change": False}
def _extract_options(data: dict[str, Any]) -> dict[str, Any]:
section = data.get("options")
if not isinstance(section, dict):
return {}
result: dict[str, Any] = {}
value = section.get("reset_exclusions_on_image_change")
if isinstance(value, bool):
result["reset_exclusions_on_image_change"] = value
return result
_CONFIG_DATA = _load_config_data() _CONFIG_DATA = _load_config_data()
DEFAULTS = {**_DEFAULTS_BASE, **_extract_default_overrides(_CONFIG_DATA)} DEFAULTS = {**_DEFAULTS_BASE, **_extract_default_overrides(_CONFIG_DATA)}
LANGUAGE = _extract_language(_CONFIG_DATA) LANGUAGE = _extract_language(_CONFIG_DATA)
OPTIONS = {**_OPTION_DEFAULTS, **_extract_options(_CONFIG_DATA)}
RESET_EXCLUSIONS_ON_IMAGE_CHANGE = OPTIONS["reset_exclusions_on_image_change"]

View File

@ -89,6 +89,14 @@ class ImageProcessingMixin:
self.image_paths = list(paths) self.image_paths = list(paths)
if not self.image_paths: if not self.image_paths:
return return
self.exclude_shapes = []
self._rubber_start = None
self._rubber_id = None
self._stroke_preview_id = None
self._exclude_canvas_ids = []
self._exclude_mask = None
self._exclude_mask_px = None
self._exclude_mask_dirty = True
self.current_image_index = -1 self.current_image_index = -1
self._display_image_by_index(max(0, start_index)) self._display_image_by_index(max(0, start_index))
@ -115,10 +123,12 @@ class ImageProcessingMixin:
self.image_path = path self.image_path = path
self.orig_img = image self.orig_img = image
self.exclude_shapes = [] if getattr(self, "reset_exclusions_on_switch", False):
self.exclude_shapes = []
self._rubber_start = None self._rubber_start = None
self._rubber_id = None self._rubber_id = None
self._stroke_preview_id = None self._stroke_preview_id = None
self._exclude_canvas_ids = []
self._exclude_mask = None self._exclude_mask = None
self._exclude_mask_px = None self._exclude_mask_px = None
self._exclude_mask_dirty = True self._exclude_mask_dirty = True

View File

@ -2,6 +2,10 @@
# language must correspond to a file name in app/lang (e.g. "en", "de"). # language must correspond to a file name in app/lang (e.g. "en", "de").
language = "en" language = "en"
[options]
# Set to true to clear exclusion shapes whenever the image changes.
reset_exclusions_on_image_change = false
[defaults] [defaults]
# Override any of the following keys to tweak the initial slider values: # Override any of the following keys to tweak the initial slider values:
# hue_min, hue_max, sat_min, val_min, val_max accept floating point numbers. # hue_min, hue_max, sat_min, val_min, val_max accept floating point numbers.