import random from typing import Tuple import numpy as np from PIL import Image from sklearn.cluster import KMeans # MARK: LUMINANCE def get_luminance(r, g, b): """returns the luminance of a color""" return 0.2126 * r + 0.7152 * g + 0.0722 * b # MARK: COMPLEMENTARY COLOR def complementary(r, g, b): """returns RGB components of complementary color""" hsv = rgb_to_hsv(r, g, b) rgb_color = hsv_to_rgb((hsv[0] + 0.5) % 1, hsv[1], hsv[2]) return round(rgb_color[0]), round(rgb_color[1]), round(rgb_color[2]) # HSV: Hue, Saturation, Value # H: position in the spectrum # S: color saturation ("purity") # V: color brightness # MARK: RGB to HSV def rgb_to_hsv(r, g, b): maxc = max(r, g, b) minc = min(r, g, b) rangec = maxc - minc v = maxc if minc == maxc: return 0.0, 0.0, v s = rangec / maxc rc = (maxc - r) / rangec gc = (maxc - g) / rangec bc = (maxc - b) / rangec if r == maxc: h = bc - gc elif g == maxc: h = 2.0 + rc - bc else: h = 4.0 + gc - rc h = (h / 6.0) % 1.0 return h, s, v # MARK: HSV to RGB def hsv_to_rgb(h, s, v): if s == 0.0: return v, v, v i = int(h * 6.0) # XXX assume int() truncates! f = (h * 6.0) - i p = v * (1.0 - s) q = v * (1.0 - s * f) t = v * (1.0 - s * (1.0 - f)) i = i % 6 if i == 0: return v, t, p if i == 1: return q, v, p if i == 2: return p, v, t if i == 3: return p, q, v if i == 4: return t, p, v if i == 5: return v, p, q # MARK: RGB to HSV def _rgb_to_hsv(rgb: np.ndarray) -> np.ndarray: """RGB (0‑255) → HSV (0‑1) – arbeitet auf (N,3) Arrays.""" rgb = rgb / 255.0 maxc = rgb.max(axis=1) minc = rgb.min(axis=1) v = maxc delta = maxc - minc # Korrektur: np.divide mit 'where' Parameter s = np.divide(delta, maxc, out=np.zeros_like(delta), where=maxc != 0) # Hue‑Berechnung rc = (maxc - rgb[:, 0]) / (delta + 1e-10) gc = (maxc - rgb[:, 1]) / (delta + 1e-10) bc = (maxc - rgb[:, 2]) / (delta + 1e-10) h = np.zeros_like(maxc) mask = delta != 0 idx = (rgb[:, 0] == maxc) & mask h[idx] = (bc - gc)[idx] idx = (rgb[:, 1] == maxc) & mask h[idx] = 2.0 + (rc - bc)[idx] idx = (rgb[:, 2] == maxc) & mask h[idx] = 4.0 + (gc - rc)[idx] h = (h / 6.0) % 1.0 return np.stack([h, s, v], axis=1) # MARK: IS COLOR GRAYSCALE def _is_grayscale(pixels: np.ndarray) -> bool: """True, wenn *alle* Pixel Graustufen (R==G==B) sind.""" return np.all(pixels[:, 0] == pixels[:, 1]) and np.all(pixels[:, 1] == pixels[:, 2]) # MARK: GET DOMINANT COLOR def get_dominant_color( image_path: str, min_brightness: float = 0.2, max_brightness: float = 0.9, n_clusters: int = 5, resize_for_speed: Tuple[int, int] = (250, 250), ) -> Tuple[int, int, int]: """ Bestimmt die dominante Farbe eines Bildes (RGB‑Tuple) innerhalb eines Helligkeitsbereichs. Wird nur Graustufen gefunden oder kein Pixel erfüllt die Helligkeitsbedingungen, liefert die Funktion eine zufällige Farbe zurück. """ # ------------------- Bild laden --------------------------------------- img = Image.open(image_path).convert("RGB") img.thumbnail(resize_for_speed) # Beschleunigt die Berechnung pixels = np.array(img).reshape(-1, 3) # (N, 3) – R,G,B # ------------------- Graustufen‑Check ------------------------------- if _is_grayscale(pixels): print("Nur Graustufen → zufällige Farbe erzeugt.") rand = [random.randint(80, 255), random.randint(80, 255), random.randint(80, 255)] rand_comp = complementary(rand[0], rand[1], rand[2]) rand_comp = [int(c) for c in rand_comp] hsv = rgb_to_hsv(rand[0], rand[1], rand[2]) rand_2 = hsv_to_rgb(hsv[0], hsv[1], 0.9) rand_2 = [int(c * 255) for c in rand_2] rand_comp_2 = complementary(rand_2[0], rand_2[1], rand_2[2]) rand_comp_2 = [int(c) for c in rand_comp_2] return [rand, rand_comp, rand_2, rand_comp_2] # ------------------- Helligkeitsfilter ------------------------------- hsv = _rgb_to_hsv(pixels) v = hsv[:, 2] # Value‑Komponente (Brightness) s = hsv[:, 1] # Value‑Komponente (Brightness) mask = (v >= min_brightness) & (v <= max_brightness) & (s >= 0.38) & (s <= 0.62) filtered_pixels = pixels[mask] if filtered_pixels.shape[0] == 0: # Keine Farbe im gewünschten Helligkeitsbereich print("Keine Farbe im gewünschten Helligkeitsbereich → zufällige Farbe erzeugt.") rand = [random.randint(80, 255), random.randint(80, 255), random.randint(80, 255)] rand_comp = complementary(rand[0], rand[1], rand[2]) rand_comp = [int(c) for c in rand_comp] hsv = rgb_to_hsv(rand[0], rand[1], rand[2]) rand_2 = hsv_to_rgb(hsv[0], hsv[1], 0.9) rand_2 = [int(c * 255) for c in rand_2] rand_comp_2 = complementary(rand_2[0], rand_2[1], rand_2[2]) rand_comp_2 = [int(c) for c in rand_comp_2] return [rand, rand_comp, rand_2, rand_comp_2] # ------------------- K‑Means (dominante Farbe) ----------------------- kmeans = KMeans(n_clusters=n_clusters, random_state=42) kmeans.fit(filtered_pixels) # Häufigstes Cluster ermitteln most_common_label = np.bincount(kmeans.labels_).argmax() dominant_rgb = kmeans.cluster_centers_[most_common_label] # Auf Integer‑Werte runden und zurückgeben dominant_rgb_1 = [int(round(c)) for c in dominant_rgb] complementary_rgb_1 = complementary(dominant_rgb_1[0], dominant_rgb_1[1], dominant_rgb_1[2]) complementary_rgb_1 = [int(c) for c in complementary_rgb_1] hsv = rgb_to_hsv(dominant_rgb_1[0], dominant_rgb_1[1], dominant_rgb_1[2]) dominant_rgb_2 = hsv_to_rgb(hsv[0], hsv[1], 0.8) dominant_rgb_2 = [int(c * 255) for c in dominant_rgb_2] complementary_rgb_2 = complementary(dominant_rgb_2[0], dominant_rgb_2[1], dominant_rgb_2[2]) complementary_rgb_2 = [int(c) for c in complementary_rgb_2] print("complementary_rgb:", complementary_rgb_2) # return dominant_rgb_1, complementary_rgb_1, dominant_rgb_2, complementary_rgb_2 return [dominant_rgb_1, complementary_rgb_1, dominant_rgb_2, complementary_rgb_2] # ------------------------------------------------------------------------- # Beispielaufruf if __name__ == "__main__": coverarts_path = "O:\\Code\\Python\\my-ai-songs\\static\\coverarts\\" coverart_file = input("CoverArt Datei: ") coverart_file = coverart_file if coverart_file.endswith(".png") else "Ein neuer Zyklus (2025) (ist nur Computer-Code).mp3.png" bild = f"{coverarts_path}{coverart_file}" # Pfad zu deinem Bild print(f"Bild: {bild}") colors = get_dominant_color(bild, min_brightness=0.7, max_brightness=0.8, n_clusters=4) print(f"Dominante Farbe (RGB): {colors[0]} - {colors[1]} - {colors[2]} - {colors[3]}")