Add saturation max slider to UI and image processing logic

This commit is contained in:
lukas 2026-03-22 17:42:26 +01:00
parent daf226a80f
commit ff9dec0eff
6 changed files with 20 additions and 7 deletions

View File

@ -39,6 +39,7 @@
"sliders.hue_min" = "Hue Min (°)"
"sliders.hue_max" = "Hue Max (°)"
"sliders.sat_min" = "Sättigung Min (%)"
"sliders.sat_max" = "Sättigung Max (%)"
"sliders.val_min" = "Helligkeit Min (%)"
"sliders.val_max" = "Helligkeit Max (%)"
"sliders.alpha" = "Overlay Alpha"

View File

@ -39,6 +39,7 @@
"sliders.hue_min" = "Hue min (°)"
"sliders.hue_max" = "Hue max (°)"
"sliders.sat_min" = "Saturation min (%)"
"sliders.sat_max" = "Saturation max (%)"
"sliders.val_min" = "Value min (%)"
"sliders.val_max" = "Value max (%)"
"sliders.alpha" = "Overlay alpha"

View File

@ -106,6 +106,7 @@ class QtImageProcessor:
"hue_min": 0,
"hue_max": 360,
"sat_min": 25,
"sat_max": 100,
"val_min": 15,
"val_max": 100,
"alpha": 120,
@ -113,6 +114,7 @@ class QtImageProcessor:
self.hue_min = self.defaults["hue_min"]
self.hue_max = self.defaults["hue_max"]
self.sat_min = self.defaults["sat_min"]
self.sat_max = self.defaults["sat_max"]
self.val_min = self.defaults["val_min"]
self.val_max = self.defaults["val_max"]
self.alpha = self.defaults["alpha"]
@ -219,6 +221,7 @@ class QtImageProcessor:
match_mask = (
hue_ok
& (sat >= float(self.sat_min))
& (sat <= float(self.sat_max))
& (val >= float(self.val_min))
& (val <= float(self.val_max))
& (alpha_ch > 0)
@ -285,6 +288,7 @@ class QtImageProcessor:
match_mask = (
hue_ok
& (sat >= float(self.sat_min))
& (sat <= float(self.sat_max))
& (val >= float(self.val_min))
& (val <= float(self.val_max))
& (alpha_ch > 0)
@ -321,7 +325,7 @@ class QtImageProcessor:
hue_ok = self.hue_min <= hue <= self.hue_max
else:
hue_ok = hue >= self.hue_min or hue <= self.hue_max
sat_ok = s * 100.0 >= self.sat_min
sat_ok = self.sat_min <= s * 100.0 <= self.sat_max
val_ok = self.val_min <= v * 100.0 <= self.val_max
return hue_ok and sat_ok and val_ok

View File

@ -41,6 +41,7 @@ SLIDER_SPECS: List[Tuple[str, str, int, int]] = [
("sliders.hue_min", "hue_min", 0, 360),
("sliders.hue_max", "hue_max", 0, 360),
("sliders.sat_min", "sat_min", 0, 100),
("sliders.sat_max", "sat_max", 0, 100),
("sliders.val_min", "val_min", 0, 100),
("sliders.val_max", "val_max", 0, 100),
("sliders.alpha", "alpha", 0, 255),
@ -917,6 +918,7 @@ class MainWindow(QtWidgets.QMainWindow, I18nMixin):
"hue_min": self.processor.hue_min,
"hue_max": self.processor.hue_max,
"sat_min": self.processor.sat_min,
"sat_max": self.processor.sat_max,
"val_min": self.processor.val_min,
"val_max": self.processor.val_max,
"alpha": self.processor.alpha,
@ -954,10 +956,12 @@ class MainWindow(QtWidgets.QMainWindow, I18nMixin):
self._update_color_display(self._current_color, self._t("palette.current"))
# 2. Apply slider values
keys = ["hue_min", "hue_max", "sat_min", "val_min", "val_max", "alpha"]
keys = ["hue_min", "hue_max", "sat_min", "sat_max", "val_min", "val_max", "alpha"]
for key in keys:
if key in settings:
setattr(self.processor, key, settings[key])
else:
setattr(self.processor, key, self.processor.defaults.get(key, 0))
# 3. Apply shapes and reference size
ref_size = None
@ -1177,13 +1181,15 @@ class MainWindow(QtWidgets.QMainWindow, I18nMixin):
margin = 15
hue_min = max(0, int(hue) - margin)
hue_max = min(360, int(hue) + margin)
# For a picked color, give a window of ±20% for saturation and -30% for value
sat_min = max(0, int(sat) - 20)
sat_max = min(100, int(sat) + 20)
val_min = max(0, int(val) - 30)
val_max = 100
for attr, value in [
("hue_min", hue_min), ("hue_max", hue_max),
("sat_min", sat_min), ("val_min", val_min), ("val_max", val_max),
("sat_min", sat_min), ("sat_max", sat_max), ("val_min", val_min), ("val_max", val_max),
]:
ctrl = self._slider_controls.get(attr)
if ctrl:

View File

@ -15,6 +15,7 @@ overlay_color = "#ff0000"
hue_min = 250.0
hue_max = 310.0
sat_min = 15.0
sat_max = 100.0
val_min = 15.0
val_max = 100.0
alpha = 120

View File

@ -12,18 +12,18 @@ def test_stats_summary():
matches_excl=10, total_excl=20
)
# Mock translator
def mock_t(key, **kwargs):
if key == "stats.placeholder":
return "Placeholder"
return f"{kwargs['with_pct']:.1f} {kwargs['without_pct']:.1f} {kwargs['excluded_pct']:.1f} {kwargs['excluded_match_pct']:.1f}"
if not kwargs:
return key
return f"{kwargs['with_pct']:.1f} {kwargs['without_pct']:.1f} {kwargs['excluded_pct']:.1f}"
res = s.summary(mock_t)
# with_pct: 40/80 = 50.0
# without_pct: 50/100 = 50.0
# excluded_pct: 20/100 = 20.0
# excluded_match_pct: 10/20 = 50.0
assert res == "50.0 50.0 20.0 50.0"
assert res == "50.0 50.0 20.0"
def test_stats_empty():
s = Stats()