""" fcco-Converter V2 – Icon-Modul Erzeugt saubere, professionelle Icons programmatisch mit PIL. Keine externen Bilddateien nötig – alle Icons sind Vektorgrafiken, die in beliebiger Größe gerendert werden können. """ from PIL import Image, ImageDraw import customtkinter as ctk def _create_canvas(size: int = 24, bg: str | None = None) -> tuple[Image.Image, ImageDraw.ImageDraw]: """Erstellt eine transparente Zeichenfläche.""" img = Image.new("RGBA", (size, size), (0, 0, 0, 0)) draw = ImageDraw.Draw(img, "RGBA") return img, draw def _hex_to_rgba(hex_color: str, alpha: int = 255) -> tuple[int, int, int, int]: """Konvertiert einen Hex-Farbwert zu RGBA.""" hex_color = hex_color.lstrip("#") r, g, b = int(hex_color[0:2], 16), int(hex_color[2:4], 16), int(hex_color[4:6], 16) return (r, g, b, alpha) def make_ctk_image(light_img: Image.Image, dark_img: Image.Image | None = None, size: tuple[int, int] = (20, 20)) -> ctk.CTkImage: """Erzeugt ein CTkImage aus PIL-Bildern.""" return ctk.CTkImage( light_image=light_img, dark_image=dark_img or light_img, size=size, ) # ========================================================================= # Icon-Generatoren # ========================================================================= def icon_folder(color: str = "#e2e8f0", size: int = 24) -> Image.Image: """Ordner-Icon.""" img, draw = _create_canvas(size) c = _hex_to_rgba(color) s = size # Ordner-Körper draw.rounded_rectangle( [s * 0.08, s * 0.25, s * 0.92, s * 0.85], radius=s * 0.08, fill=c ) # Tab oben draw.rounded_rectangle( [s * 0.08, s * 0.15, s * 0.45, s * 0.35], radius=s * 0.06, fill=c ) return img def icon_file_add(color: str = "#e2e8f0", size: int = 24) -> Image.Image: """Datei-hinzufügen-Icon.""" img, draw = _create_canvas(size) c = _hex_to_rgba(color) s = size lw = max(1, int(s * 0.08)) # Dokument draw.rounded_rectangle( [s * 0.17, s * 0.04, s * 0.71, s * 0.96], radius=s * 0.06, outline=c, width=lw ) # Plus-Zeichen cx, cy = s * 0.44, s * 0.50 arm = s * 0.15 draw.line([(cx - arm, cy), (cx + arm, cy)], fill=c, width=lw) draw.line([(cx, cy - arm), (cx, cy + arm)], fill=c, width=lw) return img def icon_play(color: str = "#e2e8f0", size: int = 24) -> Image.Image: """Play/Start-Icon (Dreieck).""" img, draw = _create_canvas(size) c = _hex_to_rgba(color) s = size # Dreieck (nach rechts zeigend) points = [ (s * 0.25, s * 0.15), (s * 0.80, s * 0.50), (s * 0.25, s * 0.85), ] draw.polygon(points, fill=c) return img def icon_settings(color: str = "#e2e8f0", size: int = 24) -> Image.Image: """Zahnrad/Einstellungen-Icon.""" img, draw = _create_canvas(size) c = _hex_to_rgba(color) s = size lw = max(1, int(s * 0.08)) cx, cy = s * 0.5, s * 0.5 # Äußerer Ring r_outer = s * 0.38 draw.ellipse( [cx - r_outer, cy - r_outer, cx + r_outer, cy + r_outer], outline=c, width=lw ) # Innerer Punkt r_inner = s * 0.12 draw.ellipse( [cx - r_inner, cy - r_inner, cx + r_inner, cy + r_inner], fill=c ) # Zähne (6 Stück als kurze Linien) import math for i in range(6): angle = math.pi * i / 3 x1 = cx + r_outer * math.cos(angle) y1 = cy + r_outer * math.sin(angle) x2 = cx + (r_outer + s * 0.12) * math.cos(angle) y2 = cy + (r_outer + s * 0.12) * math.sin(angle) draw.line([(x1, y1), (x2, y2)], fill=c, width=lw) return img def icon_refresh(color: str = "#e2e8f0", size: int = 24) -> Image.Image: """Refresh/Update-Icon (kreisförmiger Pfeil).""" img, draw = _create_canvas(size) c = _hex_to_rgba(color) s = size lw = max(1, int(s * 0.08)) # Kreisbogen draw.arc( [s * 0.15, s * 0.15, s * 0.85, s * 0.85], start=30, end=330, fill=c, width=lw ) # Pfeilspitze import math angle = math.radians(30) cx, cy = s * 0.5, s * 0.5 r = s * 0.35 tip_x = cx + r * math.cos(angle) tip_y = cy - r * math.sin(angle) arrow_size = s * 0.15 points = [ (tip_x, tip_y), (tip_x + arrow_size, tip_y + arrow_size * 0.3), (tip_x + arrow_size * 0.3, tip_y + arrow_size), ] draw.polygon(points, fill=c) return img def icon_save(color: str = "#e2e8f0", size: int = 24) -> Image.Image: """Speichern-Icon (Diskette).""" img, draw = _create_canvas(size) c = _hex_to_rgba(color) s = size lw = max(1, int(s * 0.08)) # Äußeres Rechteck draw.rounded_rectangle( [s * 0.10, s * 0.10, s * 0.90, s * 0.90], radius=s * 0.06, outline=c, width=lw ) # Inneres Rechteck (Etikett) draw.rectangle( [s * 0.30, s * 0.55, s * 0.70, s * 0.85], outline=c, width=lw ) # Oberer Bereich (Schieber) draw.rectangle( [s * 0.30, s * 0.10, s * 0.70, s * 0.38], fill=c ) return img def icon_browse(color: str = "#e2e8f0", size: int = 24) -> Image.Image: """Durchsuchen-Icon (Lupe).""" img, draw = _create_canvas(size) c = _hex_to_rgba(color) s = size lw = max(1, int(s * 0.08)) # Kreis draw.ellipse( [s * 0.12, s * 0.08, s * 0.62, s * 0.58], outline=c, width=lw ) # Griff draw.line( [(s * 0.55, s * 0.55), (s * 0.88, s * 0.88)], fill=c, width=max(2, int(s * 0.10)) ) return img def icon_check(color: str = "#34d399", size: int = 24) -> Image.Image: """Häkchen-Icon.""" img, draw = _create_canvas(size) c = _hex_to_rgba(color) s = size lw = max(2, int(s * 0.10)) draw.line( [(s * 0.18, s * 0.52), (s * 0.40, s * 0.75), (s * 0.82, s * 0.25)], fill=c, width=lw, joint="curve" ) return img def icon_cross(color: str = "#f87171", size: int = 24) -> Image.Image: """Kreuz/Fehler-Icon.""" img, draw = _create_canvas(size) c = _hex_to_rgba(color) s = size lw = max(2, int(s * 0.10)) draw.line([(s * 0.22, s * 0.22), (s * 0.78, s * 0.78)], fill=c, width=lw) draw.line([(s * 0.78, s * 0.22), (s * 0.22, s * 0.78)], fill=c, width=lw) return img def icon_music(color: str = "#e2e8f0", size: int = 24) -> Image.Image: """Musik/Audio-Icon (Notensymbol).""" img, draw = _create_canvas(size) c = _hex_to_rgba(color) s = size lw = max(1, int(s * 0.08)) # Notenhals draw.line([(s * 0.60, s * 0.15), (s * 0.60, s * 0.70)], fill=c, width=lw) # Notenkopf draw.ellipse( [s * 0.35, s * 0.60, s * 0.65, s * 0.85], fill=c ) # Fähnchen draw.arc( [s * 0.55, s * 0.12, s * 0.88, s * 0.45], start=270, end=90, fill=c, width=lw ) return img def icon_info(color: str = "#e2e8f0", size: int = 24) -> Image.Image: """Info-Icon (i im Kreis).""" img, draw = _create_canvas(size) c = _hex_to_rgba(color) s = size lw = max(1, int(s * 0.08)) # Kreis draw.ellipse( [s * 0.10, s * 0.10, s * 0.90, s * 0.90], outline=c, width=lw ) # Punkt oben draw.ellipse( [s * 0.44, s * 0.22, s * 0.56, s * 0.34], fill=c ) # Strich unten draw.line([(s * 0.50, s * 0.42), (s * 0.50, s * 0.75)], fill=c, width=lw) return img def icon_download(color: str = "#e2e8f0", size: int = 24) -> Image.Image: """Download-Icon (Pfeil nach unten).""" img, draw = _create_canvas(size) c = _hex_to_rgba(color) s = size lw = max(1, int(s * 0.08)) # Vertikale Linie draw.line([(s * 0.50, s * 0.12), (s * 0.50, s * 0.62)], fill=c, width=lw) # Pfeilspitze draw.line([(s * 0.30, s * 0.48), (s * 0.50, s * 0.68)], fill=c, width=lw) draw.line([(s * 0.70, s * 0.48), (s * 0.50, s * 0.68)], fill=c, width=lw) # Unterstrich draw.line([(s * 0.20, s * 0.85), (s * 0.80, s * 0.85)], fill=c, width=lw) return img # ========================================================================= # Vorgefertigte CTkImage-Sets (Light + Dark) # ========================================================================= class IconSet: """Hält alle Icons als CTkImage-Instanzen. Muss nach CTk-Initialisierung erstellt werden. """ def __init__(self, size: tuple[int, int] = (18, 18)): s = size[0] light = "#3f3f46" # Für hellen Modus dark = "#e2e8f0" # Für dunklen Modus accent = "#8b7cf6" success = "#34d399" error = "#f87171" self.folder = make_ctk_image(icon_folder(light, s), icon_folder(dark, s), size) self.file_add = make_ctk_image(icon_file_add(light, s), icon_file_add(dark, s), size) self.play = make_ctk_image(icon_play(light, s), icon_play(dark, s), size) self.settings = make_ctk_image(icon_settings(light, s), icon_settings(dark, s), size) self.refresh = make_ctk_image(icon_refresh(light, s), icon_refresh(dark, s), size) self.save = make_ctk_image(icon_save(light, s), icon_save(dark, s), size) self.browse = make_ctk_image(icon_browse(light, s), icon_browse(dark, s), size) self.check = make_ctk_image(icon_check(success, s), icon_check(success, s), size) self.cross = make_ctk_image(icon_cross(error, s), icon_cross(error, s), size) self.music = make_ctk_image(icon_music(light, s), icon_music(dark, s), size) self.info = make_ctk_image(icon_info(light, s), icon_info(dark, s), size) self.download = make_ctk_image(icon_download(light, s), icon_download(dark, s), size)