mirror of
https://github.com/steredbits/pelagia.git
synced 2026-10-19 06:02:40 +02:00
98 lines
3.2 KiB
Python
Executable File
98 lines
3.2 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
# Copyright (C) 2026 steredbits and Pelagia contributors
|
|
"""Generates pkg/icon0.png (512x512), the websrv tile icon of Pelagia.
|
|
|
|
Midnight-blue background, stylized jellyfish (original shape, unrelated to the
|
|
Jellyfin logo) filled with the Jellyfin color gradient, application name in
|
|
Noto Sans Bold (assets/fonts, OFL). Standard Python for the shape, ffmpeg
|
|
(drawtext) for the text.
|
|
Usage: python3 tools/make_icon.py [output] (default: pkg/icon0.png)
|
|
"""
|
|
|
|
import math
|
|
import os
|
|
import struct
|
|
import subprocess
|
|
import sys
|
|
import tempfile
|
|
import zlib
|
|
|
|
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|
SIZE = 512
|
|
BG = (16, 24, 48)
|
|
ACCENT = (0, 164, 220)
|
|
PURPLE = (170, 92, 195)
|
|
WHITE = (240, 240, 250)
|
|
|
|
|
|
def gradient(x, y):
|
|
# Diagonal purple -> blue gradient (colors of the Jellyfin gradient, whose
|
|
# brand rules allow its use; the shape below is original).
|
|
t = min(max(((x - 120) + (y - 40)) / 420.0, 0.0), 1.0)
|
|
return tuple(int(round(PURPLE[i] + (ACCENT[i] - PURPLE[i]) * t)) for i in range(3))
|
|
|
|
|
|
def in_jellyfish(x, y):
|
|
# Bell: half ellipse, scalloped lower edge; five wavy tentacles.
|
|
dx = x - 256
|
|
if y <= 190 and (dx / 132.0) ** 2 + ((y - 190) / 112.0) ** 2 <= 1.0:
|
|
return True
|
|
if 190 < y <= 214 + 7 * math.cos(dx / 132.0 * math.pi * 4) and abs(dx) <= 132 - (y - 190) * 0.6:
|
|
return True
|
|
if 214 <= y <= 350:
|
|
along = (y - 214) / 136.0
|
|
half = 7.0 - 4.0 * along
|
|
for i in range(5):
|
|
cx = 256 - 88 + 44 * i + 11 * math.sin(along * 9.0 + i * 1.3)
|
|
if abs(x - cx) <= half:
|
|
return True
|
|
return False
|
|
|
|
|
|
def pixel(x, y):
|
|
# 3x3 supersampling: smooth edges.
|
|
hits = 0
|
|
for sy in range(3):
|
|
for sx in range(3):
|
|
if in_jellyfish(x + (sx + 0.5) / 3, y + (sy + 0.5) / 3):
|
|
hits += 1
|
|
if hits == 0:
|
|
return BG
|
|
fg = gradient(x, y)
|
|
k = hits / 9.0
|
|
return tuple(int(round(BG[i] + (fg[i] - BG[i]) * k)) for i in range(3))
|
|
|
|
|
|
def write_png(path):
|
|
rows = []
|
|
for y in range(SIZE):
|
|
row = bytearray([0])
|
|
for x in range(SIZE):
|
|
row += bytes(pixel(x, y))
|
|
rows.append(bytes(row))
|
|
|
|
def chunk(kind, data):
|
|
return struct.pack(">I", len(data)) + kind + data + struct.pack(">I", zlib.crc32(kind + data) & 0xFFFFFFFF)
|
|
|
|
png = b"\x89PNG\r\n\x1a\n" + chunk(b"IHDR", struct.pack(">IIBBBBB", SIZE, SIZE, 8, 2, 0, 0, 0))
|
|
png += chunk(b"IDAT", zlib.compress(b"".join(rows), 9)) + chunk(b"IEND", b"")
|
|
with open(path, "wb") as f:
|
|
f.write(png)
|
|
|
|
|
|
def main():
|
|
out = sys.argv[1] if len(sys.argv) > 1 else os.path.join(ROOT, "pkg", "icon0.png")
|
|
font = os.path.join(ROOT, "assets", "fonts", "NotoSans-Bold.ttf")
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
base = os.path.join(tmp, "base.png")
|
|
write_png(base)
|
|
text = f"drawtext=fontfile={font}:text=Pelagia:fontsize=84:fontcolor=0xF0F0FA:x=(w-text_w)/2:y=380"
|
|
subprocess.run(["ffmpeg", "-loglevel", "error", "-y", "-i", base, "-vf", text,
|
|
"-pix_fmt", "rgb24", out], check=True)
|
|
print(out)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|