Files
1bis8/make_logo.py
T
Kimi Work 8cf4986b48
Build & Deploy / deploy (push) Successful in 19s
Red Mode: Nacht-Navigator-Logo mit 'RED RALF MODE'-Bogen
- Logo generiert: unterer Textbogen ersetzt, Goldringe rekonstruiert
- Logo in Karten-Seite + Spielraum-Lobby eingebunden
- Favicon-Swap bei aktivem Red Mode
- ?tab= URL-Param fuer Tab-Auswahl (Screenshots/Deep-Links)
2026-10-10 18:35:58 +02:00

148 lines
4.8 KiB
Python

"""Erzeugt das Red-Mode-Logo: unterer Bogen 'NACHT-NAVIGATOR' -> 'RED RALF MODE'."""
import math
from PIL import Image, ImageDraw, ImageFont
SRC = "nacht-navigator.png"
OUT_FINAL = "app/public/redmode-logo.png"
OUT_PREVIEW = "logo_preview.png"
S = 2
CS = 209
CX = CY = 104
img = Image.open(SRC).convert("RGB")
big = img.crop((0, 33, 209, 242)).resize((CS * S, CS * S), Image.LANCZOS)
cx = cy = CX * S
px = big.load()
def ang_xy(r, deg):
a = math.radians(deg)
return cx + r * math.cos(a), cy + r * math.sin(a)
def median_patch(x, y, rad=2):
vals = [[], [], []]
for dx in range(-rad, rad + 1):
for dy in range(-rad, rad + 1):
c = px[int(round(x)) + dx, int(round(y)) + dy]
for k in range(3):
vals[k].append(c[k])
return tuple(sorted(v)[len(v) // 2] for v in vals)
# ---------------------------------------------------------------- Bogen löschen
def radial_profile(deg, r0=104, r1=180, step=2):
return [median_patch(*ang_xy(r, deg)) for r in range(r0, r1 + 1, step)]
profL = radial_profile(18)
profR = radial_profile(162)
radii = list(range(104, 181, 2))
A0, A1 = 26, 154
R0, R1 = 104, 180
for y in range(cy - R1 - 8, cy + R1 + 9):
for x in range(cx - R1 - 8, cx + R1 + 9):
dx, dy = x - cx, y - cy
r = math.hypot(dx, dy)
if r < R0 or r > R1:
continue
th = math.degrees(math.atan2(dy, dx))
if th < 0:
th += 360
if not (A0 <= th <= A1):
continue
t = (th - A0) / (A1 - A0)
i = min(range(len(radii)), key=lambda k: abs(radii[k] - r))
cL, cR = profL[i], profR[i]
px[x, y] = tuple(int(cL[k] + (cR[k] - cL[k]) * t) for k in range(3))
# ------------------------------------------------- Gold-Ringe wieder anmalen
def ring_color(r0, r1):
"""Helles Gold: pro Winkel das hellste Pixel im Radius-Intervall, dann Median."""
cols = []
for deg in list(range(0, 16)) + list(range(164, 181)):
best = None
for r in range(r0, r1 + 1):
x, y = ang_xy(r, deg)
c = median_patch(x, y, 1)
lum = 0.299 * c[0] + 0.587 * c[1] + 0.114 * c[2]
if best is None or lum > best[0]:
best = (lum, c)
cols.append(best[1])
cols.sort(key=lambda c: sum(c))
return cols[int(len(cols) * 0.75)]
gold_inner = ring_color(105, 111)
gold_outer = ring_color(166, 176)
print("Gold innen:", gold_inner, "aussen:", gold_outer)
dr = ImageDraw.Draw(big)
for r, col, w in [(108, gold_inner, 7), (172, gold_outer, 9)]:
bb = (cx - r, cy - r, cx + r, cy + r)
dr.arc(bb, start=A0 - 3, end=A1 + 3, fill=col, width=w)
# ------------------------------------------------------- Bogen-Text zeichnen
FONT_PATH = "C:/Windows/Fonts/ARIALNB.TTF"
TEXT = "RED RALF MODE"
R_BASE = 156
SPAN_MAX = 100
SQUEEZE = 0.62
def char_w(fnt, ch):
box = fnt.getbbox(ch, stroke_width=0)
return box[2] - box[0]
size = 72
while size > 10:
fnt = ImageFont.truetype(FONT_PATH, size)
raw = sum(char_w(fnt, ch) for ch in TEXT)
if raw * SQUEEZE <= R_BASE * math.radians(SPAN_MAX):
break
size -= 2
print("Fontgroesse:", size, "raw width:", raw)
fnt = ImageFont.truetype(FONT_PATH, size)
items = []
for ch in TEXT:
if ch == " ":
items.append((None, int(char_w(fnt, "N") * 0.45)))
continue
box = fnt.getbbox(ch, stroke_width=4)
w, h = box[2] - box[0] + 16, box[3] - box[1] + 16
ci = Image.new("RGBA", (w, h), (0, 0, 0, 0))
cd = ImageDraw.Draw(ci)
cd.text((8 - box[0], 8 - box[1]), ch, font=fnt, fill=(248, 244, 232, 255),
stroke_width=4, stroke_fill=(22, 14, 6, 255))
ci = ci.resize((max(4, int(w * SQUEEZE)), h), Image.LANCZOS)
items.append((ci, int(char_w(fnt, ch) * SQUEEZE) + 2))
total_w = sum(w for _, w in items)
span_deg = math.degrees(total_w / R_BASE)
print("Bogen-Spanne:", round(span_deg, 1), "deg")
layer = Image.new("RGBA", big.size, (0, 0, 0, 0))
theta = math.radians(90 + span_deg / 2) # linke Bogenkante, laeuft nach rechts abnehmend
for ci, w in items:
adv = w / R_BASE # Bogenlaenge/Radius = Winkel in RADIAN
theta -= adv / 2
if ci is not None:
bx, by = ang_xy(R_BASE, math.degrees(theta))
rot = ci.rotate(90 - math.degrees(theta), expand=True, resample=Image.BICUBIC)
# Alpha-BBox: Glyph unten (Baseline) an (bx,by) ausrichten
ab = rot.getbbox()
ox = bx - (ab[0] + ab[2]) / 2
oy = by - ab[3]
layer.alpha_composite(rot, (int(ox), int(oy)))
theta -= adv / 2
out = Image.alpha_composite(big.convert("RGBA"), layer)
out.save(OUT_PREVIEW)
# ------------------------------------------------------- Alpha-Maske + speichern
final = out.copy()
mask = Image.new("L", final.size, 0)
md = ImageDraw.Draw(mask)
md.ellipse((10, 18, 408, 400), fill=255)
final.putalpha(mask)
final.save(OUT_FINAL)
print("Gespeichert:", OUT_FINAL, final.size)