Files
radiola-android/design/logos/recolor.py
nk 2fcc065a18 feat(brand): новый 3D-логотип (монограмма R) + лого/иконка под цветовую тему
Логотип: монограмма-R пользователя отрендерена в матовый 3D через routerai
(gpt-5.4-image), один мастер перекрашен под 8 тем (recolor по яркости, форма
идентична).
- Внутри приложения: AppMark показывает перекрашенный 3D-логотип текущей палитры
  (LocalThemePalette + ThemePalette.logoRes, drawable logo_<тема>).
- Иконка лаунчера следует теме: 8 adaptive-иконок (ic_fg_<тема> + ic_bg_<тема>) и
  8 activity-alias в манифесте; LauncherIconManager включает alias выбранной темы,
  гасит остальные (ровно один активен, guard против лишних миганий). Переключение —
  в MainActivity по LaunchedEffect(paletteId). На ColorOS иконка может обновляться
  с задержкой — особенность системы.
Скрипты генерации в design/logos (ключ routerai — вне репо, ~/.routerai_key).
2026-06-07 16:17:39 +03:00

75 lines
3.4 KiB
Python

# -*- coding: utf-8 -*-
"""Один мастер-логотип → 8 тем. Изолируем букву по насыщенности, перекрашиваем по яркости."""
from PIL import Image, ImageOps, ImageFilter, ImageDraw, ImageFont
import os
GEN = r"C:\radiOLA\design\logos\gen"
MASTER = os.path.join(GEN, "master_matte_0.png")
OUTDIR = os.path.join(GEN, "themed"); os.makedirs(OUTDIR, exist_ok=True)
# (id, акцент, фон темы) — как в ThemePalette
THEMES = [
("forest", (0xA8,0xE0,0x5F), (0x0C,0x14,0x10)),
("ocean", (0x4F,0xD6,0xE0), (0x0A,0x0F,0x1A)),
("sunset", (0xFF,0x8A,0x5B), (0x1A,0x0F,0x0C)),
("amethyst",(0xB3,0x88,0xFF), (0x12,0x0E,0x1A)),
("neon", (0xFF,0x4D,0x9D), (0x0D,0x0A,0x12)),
("amber", (0xFF,0xC2,0x47), (0x14,0x11,0x0A)),
("ice", (0x7F,0xB3,0xFF), (0x0C,0x10,0x14)),
("rose", (0xFF,0x7E,0xA8), (0x16,0x0E,0x12)),
]
def lerp(a,b,t): return tuple(int(a[i]+(b[i]-a[i])*t) for i in range(3))
im = Image.open(MASTER).convert("RGB")
hsv = im.convert("HSV")
H,S,V = hsv.split()
# --- альфа-маска: буква насыщенная, шахматка-фон серый (низкая S) ---
alpha = S.point(lambda s: 0 if s < 40 else min(255, int((s-40)*4)))
alpha = alpha.filter(ImageFilter.MaxFilter(3)).filter(ImageFilter.GaussianBlur(0.8))
# --- яркость буквы (3D-тени), нормализуем в рабочий диапазон ---
L = ImageOps.grayscale(im)
# статистика только по букве
import numpy as np
la = np.asarray(L, dtype=float); aa = np.asarray(alpha, dtype=float)/255.0
mask = aa > 0.5
if mask.sum() > 0:
lo, hi = np.percentile(la[mask], 4), np.percentile(la[mask], 96)
else:
lo, hi = 40, 150
la = np.clip((la - lo)/(hi - lo), 0, 1) # 0..1 нормализованная яркость буквы
Ln = Image.fromarray((la*255).astype("uint8"), "L")
def themed(accent):
dark = lerp(accent, (0,0,0), 0.55) # тень/боковая грань
light = lerp(accent, (255,255,255), 0.30) # свет фронта
col = ImageOps.colorize(Ln, black=dark, white=light, mid=accent).convert("RGBA")
col.putalpha(alpha)
return col
# отдельные прозрачные PNG + превью-лист на фоне темы
cell=300; pad=26; cols=4; rows=2
W=cols*cell+(cols+1)*pad; Hh=rows*(cell+40)+(rows+1)*pad+56
sheet=Image.new("RGB",(W,Hh),(8,12,10)); d=ImageDraw.Draw(sheet)
def f(s):
try: return ImageFont.truetype(r"C:\Windows\Fonts\segoeui.ttf",s)
except: return ImageFont.load_default()
d.text((pad,18),"radiOLA — один логотип, перекрашен под 8 тем",font=f(26),fill=(234,246,223))
for i,(name,acc,bg) in enumerate(THEMES):
logo = themed(acc)
logo.save(os.path.join(OUTDIR, f"logo_{name}.png"))
# превью на фоне темы
tile = Image.new("RGBA",(cell,cell), bg+(255,))
lg = logo.resize((int(cell*0.74),)*2)
tile.alpha_composite(lg, ((cell-lg.width)//2,(cell-lg.height)//2))
r=i//cols; c=i%cols; x=pad+c*(cell+pad); y=56+pad+r*(cell+40+pad)
sheet.paste(tile.convert("RGB"),(x,y))
d.text((x+6,y+cell+8),name,font=f(20),fill=(150,170,150))
out=os.path.join(GEN,"_themed_sheet.png"); sheet.save(out)
import shutil; shutil.copy(out, os.path.join(os.environ["LOCALAPPDATA"],"Temp","radiola_themed.png"))
print("saved", len(THEMES), "themed logos ->", OUTDIR)