tab-shooter/generate_icons.py

124 lines
3.9 KiB
Python

"""Tab Shooter — Icon Generator (pure stdlib).
Erzeugt icons/icon16.png, icon48.png, icon128.png direkt über zlib+struct.
Keine externen Abhängigkeiten (PIL nicht nötig).
"""
import os
import struct
import zlib
OUT_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "icons")
def write_png(path, width, height, rgba_pixels):
"""rgba_pixels: bytearray mit width*height*4 Bytes (RGBA)."""
def chunk(tag, data):
c = tag + data
crc = struct.pack(">I", zlib.crc32(c) & 0xFFFFFFFF)
return struct.pack(">I", len(data)) + c + crc
header = b"\x89PNG\r\n\x1a\n"
ihdr = struct.pack(">IIBBBBB", width, height, 8, 6, 0, 0, 0) # 8-bit, RGBA
# Filter byte 0 am Anfang jeder Zeile
raw = bytearray()
stride = width * 4
for y in range(height):
raw.append(0)
raw.extend(rgba_pixels[y * stride:(y + 1) * stride])
idat = zlib.compress(bytes(raw), 9)
png = header + chunk(b"IHDR", ihdr) + chunk(b"IDAT", idat) + chunk(b"IEND", b"" )
with open(path, "wb") as f:
f.write(png)
def lerp(a, b, t):
return int(a + (b - a) * t)
def lerp_color(c1, c2, t):
return tuple(lerp(c1[i], c2[i], t) for i in range(3))
def render_icon(size):
"""Zeichnet ein Raumschiff mit Cyan->Magenta Verlauf auf dunklem Grund."""
bg_top = (10, 6, 30)
bg_bot = (30, 8, 50)
cyan = (0, 240, 255)
magenta = (255, 0, 229)
yellow = (255, 170, 0)
px = bytearray(size * size * 4)
cx = (size - 1) / 2.0
nose_y = size * 0.18
base_y = size * 0.80
half_w = size * 0.34
def set_pixel(x, y, r, g, b, a=255):
if 0 <= x < size and 0 <= y < size:
i = (y * size + x) * 4
# alpha-blend over existing
if px[i + 3] == 0:
px[i:i + 4] = bytes([r, g, b, a])
else:
ia = a / 255.0
px[i] = int(r * ia + px[i] * (1 - ia))
px[i + 1] = int(g * ia + px[i + 1] * (1 - ia))
px[i + 2] = int(b * ia + px[i + 2] * (1 - ia))
px[i + 3] = min(255, px[i + 3] + a)
for y in range(size):
for x in range(size):
# Hintergrund-Verlauf (radial-ish)
t = y / max(1, size - 1)
r, g, b = lerp_color(bg_top, bg_bot, t)
# Sterne
if (x * 7 + y * 13) % 37 == 0 and size >= 48:
r, g, b = 220, 220, 255
set_pixel(x, y, r, g, b, 255)
# Raumschiff: für jede Zeile y berechne ship-x-Bereich
for y in range(size):
# relative position im Schiff (0 = nose, 1 = base)
if y < nose_y or y > base_y:
continue
t = (y - nose_y) / max(1, (base_y - nose_y))
# Schiffsbreite wächst zur Basis
w = half_w * (0.15 + 0.85 * t)
# Schiff-Verlauf cyan->magenta
cr, cg, cb = lerp_color(cyan, magenta, t)
for x in range(size):
if abs(x - cx) <= w:
# leichte Randabdunklung
edge = abs(x - cx) / max(1, w)
shade = 1.0 - edge * 0.3
set_pixel(x, y, int(cr * shade), int(cg * shade), int(cb * shade), 255)
# Triebwerk-Glühen an der Basis
if t > 0.9:
for x in range(size):
if abs(x - cx) < w * 0.5:
set_pixel(x, y, *yellow, 255)
# Cockpit (kleiner Magenta-Kreis nahe der Spitze)
cockpit_cy = int(nose_y + (base_y - nose_y) * 0.35)
cockpit_r = max(1, size // 12)
for y in range(size):
for x in range(size):
dx = x - cx
dy = y - cockpit_cy
if dx * dx + dy * dy <= cockpit_r * cockpit_r:
set_pixel(x, y, *magenta, 255)
return px
def main():
os.makedirs(OUT_DIR, exist_ok=True)
for s in (16, 48, 128):
pixels = render_icon(s)
path = os.path.join(OUT_DIR, f"icon{s}.png")
write_png(path, s, s, pixels)
print(f"Wrote {path}")
if __name__ == "__main__":
main()