124 lines
		
	
	
		
			4.3 KiB
		
	
	
	
		
			Python
		
	
	
	
			
		
		
	
	
			124 lines
		
	
	
		
			4.3 KiB
		
	
	
	
		
			Python
		
	
	
	
| """Color selection utilities."""
 | |
| 
 | |
| from __future__ import annotations
 | |
| 
 | |
| import colorsys
 | |
| 
 | |
| from tkinter import colorchooser, messagebox
 | |
| 
 | |
| 
 | |
| class ColorPickerMixin:
 | |
|     """Handles colour selection from dialogs and mouse clicks."""
 | |
| 
 | |
|     ref_hue: float | None
 | |
|     hue_span: float = 45.0  # degrees around the picked hue
 | |
| 
 | |
|     def choose_color(self):
 | |
|         rgb, hex_colour = colorchooser.askcolor(title="Farbe wählen")
 | |
|         if rgb is None:
 | |
|             return
 | |
|         r, g, b = (int(round(channel)) for channel in rgb)
 | |
|         hue_deg, sat_pct, val_pct = self._apply_rgb_selection(r, g, b)
 | |
|         label = hex_colour or f"RGB({r}, {g}, {b})"
 | |
|         self.status.config(
 | |
|             text=f"Farbe gewählt: {label} — Hue {hue_deg:.1f}°, S {sat_pct:.0f}%, V {val_pct:.0f}%"
 | |
|         )
 | |
| 
 | |
|     def apply_sample_colour(self, hex_colour: str, name: str | None = None) -> None:
 | |
|         """Apply a predefined colour preset."""
 | |
|         rgb = self._parse_hex_colour(hex_colour)
 | |
|         if rgb is None:
 | |
|             return
 | |
|         hue_deg, sat_pct, val_pct = self._apply_rgb_selection(*rgb)
 | |
|         if self.pick_mode:
 | |
|             self.pick_mode = False
 | |
|         label = name or hex_colour.upper()
 | |
|         self.status.config(
 | |
|             text=(
 | |
|                 f"Beispielfarbe gewählt: {label} ({hex_colour}) — "
 | |
|                 f"Hue {hue_deg:.1f}°, S {sat_pct:.0f}%, V {val_pct:.0f}%"
 | |
|             )
 | |
|         )
 | |
| 
 | |
|     def enable_pick_mode(self):
 | |
|         if self.preview_img is None:
 | |
|             messagebox.showinfo("Info", "Bitte zuerst ein Bild laden.")
 | |
|             return
 | |
|         self.pick_mode = True
 | |
|         self.status.config(text="Pick-Modus: Klicke links ins Bild, um Farbe zu wählen (Esc beendet)")
 | |
| 
 | |
|     def disable_pick_mode(self, event=None):
 | |
|         if self.pick_mode:
 | |
|             self.pick_mode = False
 | |
|             self.status.config(text="Pick-Modus beendet.")
 | |
| 
 | |
|     def on_canvas_click(self, event):
 | |
|         if not self.pick_mode or self.preview_img is None:
 | |
|             return
 | |
|         x = int(event.x)
 | |
|         y = int(event.y)
 | |
|         if x < 0 or y < 0 or x >= self.preview_img.width or y >= self.preview_img.height:
 | |
|             return
 | |
|         r, g, b, a = self.preview_img.getpixel((x, y))
 | |
|         if a == 0:
 | |
|             return
 | |
|         hue_deg, sat_pct, val_pct = self._apply_rgb_selection(r, g, b)
 | |
|         self.disable_pick_mode()
 | |
|         self.status.config(
 | |
|             text=f"Farbe vom Bild gewählt: Hue {hue_deg:.1f}°, S {sat_pct:.0f}%, V {val_pct:.0f}%"
 | |
|         )
 | |
| 
 | |
|     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."""
 | |
|         h, s, v = colorsys.rgb_to_hsv(r / 255.0, g / 255.0, b / 255.0)
 | |
|         hue_deg = (h * 360.0) % 360.0
 | |
|         self.ref_hue = hue_deg
 | |
|         self._set_slider_targets(hue_deg, s, v)
 | |
|         self.update_preview()
 | |
|         return hue_deg, s * 100.0, v * 100.0
 | |
| 
 | |
|     def _set_slider_targets(self, hue_deg: float, saturation: float, value: float) -> None:
 | |
|         span = getattr(self, "hue_span", 45.0)
 | |
|         self.hue_min.set((hue_deg - span) % 360)
 | |
|         self.hue_max.set((hue_deg + span) % 360)
 | |
| 
 | |
|         sat_pct = saturation * 100.0
 | |
|         sat_margin = 35.0
 | |
|         sat_min = max(0.0, min(100.0, sat_pct - sat_margin))
 | |
|         if saturation <= 0.05:
 | |
|             sat_min = 0.0
 | |
|         self.sat_min.set(sat_min)
 | |
| 
 | |
|         v_pct = value * 100.0
 | |
|         val_margin = 35.0
 | |
|         val_min = max(0.0, v_pct - val_margin)
 | |
|         val_max = min(100.0, v_pct + val_margin)
 | |
|         if value <= 0.15:
 | |
|             val_max = min(45.0, max(val_max, 25.0))
 | |
|         if value >= 0.85:
 | |
|             val_min = max(55.0, min(val_min, 80.0))
 | |
|         if val_max <= val_min:
 | |
|             val_max = min(100.0, val_min + 10.0)
 | |
|         self.val_min.set(val_min)
 | |
|         self.val_max.set(val_max)
 | |
| 
 | |
|     @staticmethod
 | |
|     def _parse_hex_colour(hex_colour: str | None) -> tuple[int, int, int] | None:
 | |
|         if not hex_colour:
 | |
|             return None
 | |
|         value = hex_colour.strip().lstrip("#")
 | |
|         if len(value) == 3:
 | |
|             value = "".join(ch * 2 for ch in value)
 | |
|         if len(value) != 6:
 | |
|             return None
 | |
|         try:
 | |
|             r = int(value[0:2], 16)
 | |
|             g = int(value[2:4], 16)
 | |
|             b = int(value[4:6], 16)
 | |
|         except ValueError:
 | |
|             return None
 | |
|         return r, g, b
 | |
| 
 | |
| 
 | |
| __all__ = ["ColorPickerMixin"]
 |