124 lines
3.9 KiB
Python
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()
|