Files
ink-notes/scripts/generate_png_icon.py
harald dfb5a64917
Build & Publish Release / Build Packages & Publish to Gitea (push) Successful in 5m26s
feat(release): Packaging für Zorin OS 18 (.deb & .AppImage), In-App Gitea Updater und CI/CD Workflow
2026-10-06 11:30:04 +02:00

193 lines
6.1 KiB
Python

#!/usr/bin/env python3
"""
Erzeugt ein 256x256 PNG-Icon für ink-notes mit Python Standard-Bibliothek (ohne externe Module).
"""
import math
import os
import struct
import zlib
WIDTH = 256
HEIGHT = 256
def make_png(width, height, rgba_data):
def chunk(tag, data):
return (
struct.pack(">I", len(data))
+ tag
+ data
+ struct.pack(">I", zlib.crc32(tag + data) & 0xFFFFFFFF)
)
header = b"\x89PNG\r\n\x1a\n"
ihdr = chunk(tag=b"IHDR", data=struct.pack(">IIBBBBB", width, height, 8, 6, 0, 0, 0))
raw = bytearray()
for y in range(height):
raw.append(0) # Filter type 0 (None)
raw.extend(rgba_data[y * width * 4 : (y + 1) * width * 4])
idat = chunk(tag=b"IDAT", data=zlib.compress(bytes(raw), 9))
iend = chunk(tag=b"IEND", data=b"")
return header + ihdr + idat + iend
def render_icon():
buffer = bytearray(WIDTH * HEIGHT * 4)
def set_pixel(x, y, r, g, b, a):
if 0 <= x < WIDTH and 0 <= y < HEIGHT:
idx = (y * WIDTH + x) * 4
src_a = a / 255.0
dst_a = buffer[idx + 3] / 255.0
out_a = src_a + dst_a * (1.0 - src_a)
if out_a > 1e-5:
out_r = (r * src_a + buffer[idx] * dst_a * (1.0 - src_a)) / out_a
out_g = (
g * src_a + buffer[idx + 1] * dst_a * (1.0 - src_a)
) / out_a
out_b = (
b * src_a + buffer[idx + 2] * dst_a * (1.0 - src_a)
) / out_a
buffer[idx] = int(round(out_r))
buffer[idx + 1] = int(round(out_g))
buffer[idx + 2] = int(round(out_b))
buffer[idx + 3] = int(round(out_a * 255.0))
# 1. Abgerundete Squircle-Kachel (Zorin OS Stil)
cx, cy = WIDTH / 2.0, HEIGHT / 2.0
half_size = 114.0
radius = 52.0
for y in range(HEIGHT):
for x in range(WIDTH):
dx = abs(x - cx)
dy = abs(y - cy)
# Distance from rounded box
qx = max(0.0, dx - (half_size - radius))
qy = max(0.0, dy - (half_size - radius))
dist = math.sqrt(qx * qx + qy * qy)
if dx <= half_size and dy <= half_size and dist <= radius:
# Gradient von Dunkelblau (#1e2638) nach Nachtblau (#0f1422)
t = (x + y) / (WIDTH + HEIGHT)
r = int(30 * (1 - t) + 12 * t)
g = int(38 * (1 - t) + 16 * t)
b = int(58 * (1 - t) + 28 * t)
# Rand-Antialiasing
edge_dist = radius - dist
alpha = min(255, max(0, int(edge_dist * 255)))
# Subtiler Rahmen
if edge_dist < 2.5:
r, g, b = 60, 75, 110
set_pixel(x, y, r, g, b, alpha)
# 2. Subtile Gitterlinien
for gy in range(80, 200, 30):
for gx in range(40, 216):
set_pixel(gx, gy, 140, 160, 190, 25)
for gx in range(70, 200, 30):
for gy in range(50, 216):
set_pixel(gx, gy, 140, 160, 190, 25)
# 3. Tintenspur (Bézier-Kurve mit Glow)
pts = []
for t_i in range(150):
t = t_i / 150.0
# P0=(55, 195), P1=(90, 215), P2=(130, 190), P3=(120, 155), P4=(140, 110), P5=(170, 110)
# 2 Teilkurven
if t <= 0.5:
u = t * 2.0
bx = (
(1 - u) ** 3 * 55
+ 3 * (1 - u) ** 2 * u * 90
+ 3 * (1 - u) * u**2 * 130
+ u**3 * 120
)
by = (
(1 - u) ** 3 * 195
+ 3 * (1 - u) ** 2 * u * 215
+ 3 * (1 - u) * u**2 * 190
+ u**3 * 155
)
else:
u = (t - 0.5) * 2.0
bx = (
(1 - u) ** 3 * 120
+ 3 * (1 - u) ** 2 * u * 110
+ 3 * (1 - u) * u**2 * 140
+ u**3 * 170
)
by = (
(1 - u) ** 3 * 155
+ 3 * (1 - u) ** 2 * u * 120
+ 3 * (1 - u) * u**2 * 95
+ u**3 * 110
)
pts.append((bx, by))
for px, py in pts:
for ox in range(-7, 8):
for oy in range(-7, 8):
d = math.sqrt(ox * ox + oy * oy)
if d <= 7.0:
a = int((1.0 - d / 7.0) * 110)
set_pixel(int(px + ox), int(py + oy), 59, 130, 246, a)
for ox in range(-3, 4):
for oy in range(-3, 4):
d = math.sqrt(ox * ox + oy * oy)
if d <= 3.0:
a = int((1.0 - d / 3.0) * 230)
set_pixel(int(px + ox), int(py + oy), 147, 197, 253, a)
# 4. Füllfederhalter (diagonale Geometrie)
# Stift-Mittelachse von (120, 55) nach (170, 110)
pen_dir = (math.cos(math.radians(45)), math.sin(math.radians(45)))
norm = (-pen_dir[1], pen_dir[0])
# Spitze bei (110, 195)
tip_x, tip_y = 55.0, 195.0
for l in range(0, 120):
pos_x = tip_x + pen_dir[0] * l
pos_y = tip_y - pen_dir[1] * l
# Radius entlang des Stifts
if l < 20:
# Goldene Feder
w = (l / 20.0) * 10.0
color = (245, 158, 11) # Gold
elif l < 32:
# Griffzone
w = 11.0
color = (30, 41, 59) # Dunkelgrau
else:
# Schaft
w = 12.0
color = (59, 130, 246) # Blau
for s in range(int(-w), int(w) + 1):
px = int(pos_x + norm[0] * s)
py = int(pos_y + norm[1] * s)
set_pixel(px, py, color[0], color[1], color[2], 255)
# 5. Glanzpunkt auf der Spitze
for ox in range(-4, 5):
for oy in range(-4, 5):
d = math.sqrt(ox * ox + oy * oy)
if d <= 4:
set_pixel(int(tip_x + ox), int(tip_y + oy), 255, 255, 255, 255)
return make_png(WIDTH, HEIGHT, buffer)
if __name__ == "__main__":
os.makedirs("assets", exist_ok=True)
png_data = render_icon()
with open("assets/ink-notes.png", "wb") as f:
f.write(png_data)
print("PNG-Icon erfolgreich generiert: assets/ink-notes.png (256x256)")