Das Logo lag bisher nirgends im Einsatz — das Favicon war ein handgeschriebenes Prisma in Gold/Orange, das Wasserzeichen der Willkommens-Karte der blosse Markenname als Text. - docs/brand/ mit lockup.png (volles Logo) und mark.png (nur der Wolfskopf) plus build.py, das beide und alle abgeleiteten Icons erzeugt - frontend: favicon.svg ersetzt, apple-touch-icon.png dazu und verlinkt - Zwei Fassungen, weil das Lockup bei 16 px zu einem orangen Balken wird; der Kopfzuschnitt wurde gegen drei Varianten geprueft - SVG mit eingebettetem PNG statt Vektorisierung: das Original hat Verlaeufe und Schattierung, nachgezeichnet wuerde es matschen - Die Icons auf dunklem Grund sind deckend, sonst legt iOS hinter transparente Lesezeichen Schwarz oder Weiss und der Wolf ist selbst fast schwarz Geprueft: beide Dateien werden mit korrektem MIME-Typ ausgeliefert und das SVG rendert wirklich (128x128), die Link-Tags stehen im gebauten HTML. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
108 lines
4.5 KiB
Python
108 lines
4.5 KiB
Python
# Baut aus dem Original-Logo den Satz an Marken-Dateien:
|
|
# - die Wolfskopf-Marke freigestellt (fuer Favicon, Avatar, kleine Groessen)
|
|
# - das volle Lockup beschnitten (fuer Kopfzeilen, Discord, OG-Bilder)
|
|
# - Icons auf dunklem Grund (iOS und Gitea-Standardavatar moegen kein Alpha)
|
|
# - SVG-Huellen mit eingebettetem PNG (Gitea will .svg, das Original ist
|
|
# eine Rastergrafik mit Verlaeufen — nachvektorisieren wuerde matschen)
|
|
import base64
|
|
import io
|
|
import pathlib
|
|
|
|
from PIL import Image, ImageDraw
|
|
|
|
QUELLE = pathlib.Path('A:/Users/DAR/logo.png')
|
|
MARKE = pathlib.Path('A:/eco/dc_devlog_bot/docs/brand')
|
|
GITEA = pathlib.Path('A:/eco/Gitea/data/gitea/public/assets/img')
|
|
FRONTEND = pathlib.Path('A:/eco/dc_devlog_bot/frontend/public')
|
|
|
|
# Kopf mit Ohrenspitzen und etwas Schulter, ohne Schriftzug (im 2048er Original)
|
|
KOPF = (330, 20, 1740, 1430)
|
|
DUNKEL = (10, 10, 10, 255) # --bg der Marke
|
|
|
|
for d in (MARKE, GITEA, FRONTEND):
|
|
d.mkdir(parents=True, exist_ok=True)
|
|
|
|
original = Image.open(QUELLE).convert('RGBA')
|
|
marke = original.crop(KOPF)
|
|
lockup = original.crop(original.getchannel('A').getbbox())
|
|
|
|
|
|
def png_bytes(bild, groesse=None):
|
|
aus = bild if groesse is None else bild.resize((groesse, groesse), Image.LANCZOS)
|
|
puffer = io.BytesIO()
|
|
aus.save(puffer, 'PNG', optimize=True)
|
|
return puffer.getvalue()
|
|
|
|
|
|
def gerundet(bild, kante, radius_anteil=0.22, rand=0.08):
|
|
"""Marke mittig auf dunklem, abgerundetem Quadrat — deckend."""
|
|
flaeche = Image.new('RGBA', (kante, kante), (0, 0, 0, 0))
|
|
maske = Image.new('L', (kante, kante), 0)
|
|
ImageDraw.Draw(maske).rounded_rectangle(
|
|
(0, 0, kante - 1, kante - 1), radius=int(kante * radius_anteil), fill=255)
|
|
flaeche.paste(Image.new('RGBA', (kante, kante), DUNKEL), (0, 0), maske)
|
|
|
|
innen = int(kante * (1 - 2 * rand))
|
|
klein = bild.resize((innen, innen), Image.LANCZOS)
|
|
flaeche.alpha_composite(klein, ((kante - innen) // 2, (kante - innen) // 2))
|
|
return flaeche
|
|
|
|
|
|
def svg_huelle(daten, kante):
|
|
"""SVG mit eingebettetem PNG — gueltig, und Gitea nimmt nur .svg."""
|
|
b64 = base64.b64encode(daten).decode('ascii')
|
|
return (
|
|
f'<svg xmlns="http://www.w3.org/2000/svg" '
|
|
f'xmlns:xlink="http://www.w3.org/1999/xlink" '
|
|
f'viewBox="0 0 {kante} {kante}" width="{kante}" height="{kante}">'
|
|
f'<image width="{kante}" height="{kante}" '
|
|
f'xlink:href="data:image/png;base64,{b64}"/>'
|
|
f'</svg>\n'
|
|
)
|
|
|
|
|
|
geschrieben = []
|
|
|
|
|
|
def schreib(pfad, daten):
|
|
pfad.write_bytes(daten if isinstance(daten, bytes) else daten.encode('utf-8'))
|
|
geschrieben.append((pfad, len(daten)))
|
|
|
|
|
|
# ── Quelldateien im Repo, versioniert ────────────────
|
|
schreib(MARKE / 'mark.png', png_bytes(marke, 1024))
|
|
schreib(MARKE / 'lockup.png', png_bytes(lockup))
|
|
|
|
# ── Gitea ────────────────────────────────────────────
|
|
# Navbar-Logo und Favicon: die Marke, das Lockup ist bei 30 px unlesbar.
|
|
# 192 px reichen — Gitea zeigt das Logo in der Navbar mit rund 30 px, und die
|
|
# Datei haengt an jedem Seitenaufruf.
|
|
schreib(GITEA / 'logo.svg', svg_huelle(png_bytes(marke, 192), 192))
|
|
schreib(GITEA / 'favicon.svg', svg_huelle(png_bytes(gerundet(marke, 128), 128), 128))
|
|
schreib(GITEA / 'apple-touch-icon.png', png_bytes(gerundet(marke, 180)))
|
|
schreib(GITEA / 'avatar_default.png', png_bytes(gerundet(marke, 512)))
|
|
|
|
# ── Hub + Bot-Produktseite ───────────────────────────
|
|
schreib(FRONTEND / 'favicon.svg', svg_huelle(png_bytes(gerundet(marke, 128), 128), 128))
|
|
schreib(FRONTEND / 'apple-touch-icon.png', png_bytes(gerundet(marke, 180)))
|
|
|
|
for pfad, n in geschrieben:
|
|
print(f'{n/1024:8.1f} KB {pfad}')
|
|
|
|
# ── Vorschau: liest die Marke in klein noch? ─────────
|
|
# Landet neben diesem Skript, oben auf hellem, unten auf dunklem Grund —
|
|
# so sieht man vor dem Ausrollen, ab welcher Groesse es kippt.
|
|
KANTEN = (16, 32, 64, 128)
|
|
breite = sum(k + 40 for k in KANTEN) + 30
|
|
vorschau = Image.new('RGBA', (breite, 200), (24, 24, 28, 255))
|
|
x = 30
|
|
for kante in KANTEN:
|
|
for grund, y in (((250, 250, 250, 255), 20), (DUNKEL, 110)):
|
|
feld = Image.new('RGBA', (kante + 16, kante + 16), grund)
|
|
feld.alpha_composite(gerundet(marke, kante), (8, 8))
|
|
vorschau.alpha_composite(feld, (x, y))
|
|
x += kante + 40
|
|
ziel = pathlib.Path(__file__).parent / 'preview.png'
|
|
vorschau.convert('RGB').save(ziel)
|
|
print(f'\nVorschau ({"/".join(map(str, KANTEN))} px): {ziel}')
|