#!/usr/bin/env python3
"""Génère toutes les icônes XtremFlow à partir d'un seul dessin vectoriel.
Palette alignée sur lib/core/theme/app_colors.dart (graphite + bleu Apple).
Sorties :
assets/branding/*.svg sources vectorielles
assets/images/logo_xtremflow.png logo in-app (fond transparent)
assets/images/app_icon_ios.png icône iOS (carré plein, sans alpha)
android/.../mipmap-*/ic_launcher.png icône legacy (tuile arrondie)
android/.../mipmap-*/ic_launcher_round.png icône legacy ronde
android/.../drawable-*/ic_launcher_foreground.png calque adaptatif
android/.../drawable-xhdpi/banner.png bannière Android TV / Fire TV
Usage : pip install cairosvg pillow && python3 scripts/generate_icons.py
La bannière utilise la police Inter (Bold) si elle est installée.
"""
import io
import os
import cairosvg
from PIL import Image
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
RES = os.path.join(ROOT, "android", "app", "src", "main", "res")
# Couleurs du thème (app_colors.dart)
BG_TOP = "#2C2C33" # surfaceContainerLow
BG_BOTTOM = "#1B1B1F" # baseLevel0
BLUE = "#0A84FF" # primaryContainer
BLUE_LIGHT = "#8AB9FF" # primary
CYAN = "#7DD3F2" # tertiary
DEFS = f"""
"""
def mark(cx, cy, size):
"""Le symbole : un bouton play fluide précédé de traînées de vitesse.
Dessiné dans une boîte de 100x100 centrée sur (cx, cy), mise à l'échelle
à `size` pixels.
"""
s = size / 100.0
tx, ty = cx - 50 * s, cy - 50 * s
return f"""
"""
def svg(w, h, body):
return (
f'\n'
)
def tile(size, radius_ratio, mark_ratio=0.64):
"""Tuile graphite (arrondie ou pleine) avec le symbole centré."""
r = size * radius_ratio
body = f"""
{mark(size / 2, size / 2, size * mark_ratio)}"""
return svg(size, size, body)
def round_tile(size):
body = f"""
{mark(size / 2, size / 2, size * 0.62)}"""
return svg(size, size, body)
def adaptive_foreground(size):
# 108dp de canevas, zone sûre de 66dp : le symbole occupe ~58% du canevas,
# soit l'intérieur du cercle sûr quelle que soit la forme du masque.
return svg(size, size, mark(size / 2, size / 2, size * 0.58))
def banner(w=320, h=180):
"""Bannière TV (Android TV / Fire TV) : 320x180 px en xhdpi."""
body = f"""
{mark(w * 0.25, h / 2, h * 0.56)}
XtremFlow
IPTV PLAYER"""
return svg(w, h, body)
def render(svg_text, out_path, size=None, flatten=None):
png = cairosvg.svg2png(
bytestring=svg_text.encode(),
output_width=size[0] if size else None,
output_height=size[1] if size else None,
)
img = Image.open(io.BytesIO(png)).convert("RGBA")
if flatten:
bg = Image.new("RGBA", img.size, flatten)
bg.alpha_composite(img)
img = bg.convert("RGB")
os.makedirs(os.path.dirname(out_path), exist_ok=True)
img.save(out_path, optimize=True)
print(" ", os.path.relpath(out_path, ROOT), img.size)
def main():
brand = os.path.join(ROOT, "assets", "branding")
os.makedirs(brand, exist_ok=True)
sources = {
"icon_rounded.svg": tile(1024, 0.225),
"icon_square.svg": tile(1024, 0),
"icon_round.svg": round_tile(1024),
"icon_foreground.svg": adaptive_foreground(1024),
"tv_banner.svg": banner(),
}
for name, text in sources.items():
with open(os.path.join(brand, name), "w") as f:
f.write(text)
print("Assets Flutter :")
render(sources["icon_rounded.svg"], os.path.join(ROOT, "assets/images/logo_xtremflow.png"), (512, 512))
# iOS applique son propre masque et refuse l'alpha : carré plein.
render(sources["icon_square.svg"], os.path.join(ROOT, "assets/images/app_icon_ios.png"), (1024, 1024), flatten=BG_BOTTOM)
print("Android :")
densities = {"mdpi": 1, "hdpi": 1.5, "xhdpi": 2, "xxhdpi": 3, "xxxhdpi": 4}
for d, k in densities.items():
legacy = round(48 * k)
fg = round(108 * k)
render(sources["icon_rounded.svg"], f"{RES}/mipmap-{d}/ic_launcher.png", (legacy, legacy))
render(sources["icon_round.svg"], f"{RES}/mipmap-{d}/ic_launcher_round.png", (legacy, legacy))
render(sources["icon_foreground.svg"], f"{RES}/drawable-{d}/ic_launcher_foreground.png", (fg, fg))
# Bannière TV : 160x90dp -> 320x180 en xhdpi (taille de référence Google/Amazon).
render(sources["tv_banner.svg"], f"{RES}/drawable-xhdpi/banner.png", (320, 180), flatten=BG_BOTTOM)
render(sources["tv_banner.svg"], f"{RES}/drawable-xxhdpi/banner.png", (480, 270), flatten=BG_BOTTOM)
render(sources["tv_banner.svg"], f"{RES}/drawable-xxxhdpi/banner.png", (640, 360), flatten=BG_BOTTOM)
# Aperçu grand format pour les stores / README.
render(sources["tv_banner.svg"], os.path.join(brand, "tv_banner_1280x720.png"), (1280, 720), flatten=BG_BOTTOM)
if __name__ == "__main__":
main()