"""Generuje ikonki Goona. Output: mobile/assets/icon.png — 1024x1024, full square (iOS + fallback) mobile/assets/adaptive-icon.png — 1024x1024, foreground only (środek 66% safe) mobile/assets/splash.png — 1024x1024, splash screen mobile/assets/favicon.png — 48x48 (web) Design: - Background: dark gradient (#0E1018 → #1A1D2A) — pasuje do theme.bg/card - Glyph: lowercase "g" w purple `#8B5CF6` (theme.accent) z accentGlow soft halo - Adaptive icon: TYLKO glyph (Android docina canvas do koła/kwadratu z border radius) """ from __future__ import annotations from pathlib import Path from PIL import Image, ImageDraw, ImageFilter, ImageFont OUT = Path(__file__).resolve().parents[1] / "mobile" / "assets" OUT.mkdir(parents=True, exist_ok=True) ACCENT = (139, 92, 246, 255) # #8B5CF6 ACCENT_GLOW = (167, 139, 250, 255) # #A78BFA BG_TOP = (14, 16, 24, 255) # #0E1018 BG_BOT = (26, 29, 42, 255) # #1A1D2A TRANSPARENT = (0, 0, 0, 0) def _vertical_gradient(size: int, top: tuple, bot: tuple) -> Image.Image: img = Image.new("RGBA", (size, size), top) px = img.load() for y in range(size): t = y / (size - 1) r = int(top[0] * (1 - t) + bot[0] * t) g = int(top[1] * (1 - t) + bot[1] * t) b = int(top[2] * (1 - t) + bot[2] * t) for x in range(size): px[x, y] = (r, g, b, 255) return img def _find_font(size: int) -> ImageFont.FreeTypeFont: candidates = [ "C:/Windows/Fonts/segoeuib.ttf", # Segoe UI Bold "C:/Windows/Fonts/Arial Bold.ttf", "C:/Windows/Fonts/arialbd.ttf", "C:/Windows/Fonts/calibrib.ttf", ] for path in candidates: try: return ImageFont.truetype(path, size) except (OSError, IOError): continue return ImageFont.load_default() def _draw_g(canvas: Image.Image, *, size: int, glow: bool) -> None: """Rysuje lowercase 'g' centered, z opcjonalnym glow halo.""" glyph_size = int(size * 0.78) font = _find_font(glyph_size) # 'g' lowercase ma descender (idzie poniżej baseline) — używamy textbbox żeby # poprawnie wycentrować całość pionowo. bbox = font.getbbox("g") text_w = bbox[2] - bbox[0] text_h = bbox[3] - bbox[1] x = (size - text_w) // 2 - bbox[0] y = (size - text_h) // 2 - bbox[1] if glow: # Glow: rysuj tę samą literę w accentGlow z dużym blur, alpha~50% glow_layer = Image.new("RGBA", (size, size), TRANSPARENT) gdraw = ImageDraw.Draw(glow_layer) gdraw.text((x, y), "g", font=font, fill=(*ACCENT_GLOW[:3], 128)) glow_layer = glow_layer.filter(ImageFilter.GaussianBlur(size * 0.04)) canvas.alpha_composite(glow_layer) draw = ImageDraw.Draw(canvas) draw.text((x, y), "g", font=font, fill=ACCENT) def make_icon(size: int = 1024) -> Image.Image: """Pełna ikonka: gradient bg + glyph z glow.""" img = _vertical_gradient(size, BG_TOP, BG_BOT) _draw_g(img, size=size, glow=True) return img def make_adaptive_foreground(size: int = 1024) -> Image.Image: """Adaptive icon foreground: TYLKO glyph na transparent. Android docina canvas do circle/squircle/rounded-square — glyph musi mieścić się w 66% środkowych pikseli.""" img = Image.new("RGBA", (size, size), TRANSPARENT) _draw_g(img, size=size, glow=True) return img def make_splash(size: int = 1024) -> Image.Image: """Splash screen — full square with gradient + centered glyph (mniejszy).""" img = _vertical_gradient(size, BG_TOP, BG_BOT) # mniejszy glyph dla splash (centered ~ 50%) glyph_size = int(size * 0.5) font = _find_font(glyph_size) bbox = font.getbbox("g") text_w = bbox[2] - bbox[0] text_h = bbox[3] - bbox[1] x = (size - text_w) // 2 - bbox[0] y = (size - text_h) // 2 - bbox[1] glow_layer = Image.new("RGBA", (size, size), TRANSPARENT) gdraw = ImageDraw.Draw(glow_layer) gdraw.text((x, y), "g", font=font, fill=(*ACCENT_GLOW[:3], 110)) glow_layer = glow_layer.filter(ImageFilter.GaussianBlur(size * 0.05)) img.alpha_composite(glow_layer) draw = ImageDraw.Draw(img) draw.text((x, y), "g", font=font, fill=ACCENT) return img def main() -> None: icon = make_icon(1024) icon.save(OUT / "icon.png", "PNG") print(f"wrote {OUT / 'icon.png'}") adaptive = make_adaptive_foreground(1024) adaptive.save(OUT / "adaptive-icon.png", "PNG") print(f"wrote {OUT / 'adaptive-icon.png'}") splash = make_splash(1024) splash.save(OUT / "splash.png", "PNG") print(f"wrote {OUT / 'splash.png'}") favicon = icon.resize((48, 48), Image.LANCZOS) favicon.save(OUT / "favicon.png", "PNG") print(f"wrote {OUT / 'favicon.png'}") if __name__ == "__main__": main()