Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01K6897PWcUEwHiqZMcVnNF6
This commit is contained in:
1 parent
4bc0dacd97
commit
f718d1869e
1 file changed
+45
-24
+45
-24
@@ -2,41 +2,62 @@
|
||||
|
||||
python3 tools/icons.py -> icons/icon-512.png, icons/icon-192.png, icons/apple-touch-icon.png
|
||||
|
||||
Needs Pillow (pip install pillow). Drawn at 4x and downsampled so the edges are clean.
|
||||
Standard library only: three rotated rectangles, 3x3 supersampled, written as RGB PNG.
|
||||
"""
|
||||
import math
|
||||
import sys
|
||||
import struct
|
||||
import zlib
|
||||
from pathlib import Path
|
||||
|
||||
try:
|
||||
from PIL import Image, ImageDraw
|
||||
except ImportError: # pragma: no cover
|
||||
sys.exit("pip install pillow")
|
||||
|
||||
ROOT = Path(__file__).resolve().parent.parent
|
||||
GROUND, INK = (11, 11, 14, 255), (237, 240, 240, 255)
|
||||
GROUND, INK = (11, 11, 14), (237, 240, 240)
|
||||
SS = 3
|
||||
|
||||
|
||||
def asterisk(size: int) -> Image.Image:
|
||||
ss = 4
|
||||
W = size * ss
|
||||
im = Image.new("RGBA", (W, W), GROUND)
|
||||
d = ImageDraw.Draw(im)
|
||||
c = W / 2
|
||||
half, width = 150 / 512 * W, 58 / 512 * W # arm half-length and stroke width, as in icon.svg
|
||||
for k in range(3):
|
||||
a = math.radians(90 + 60 * k)
|
||||
dx, dy = math.cos(a), math.sin(a)
|
||||
nx, ny = -dy * width / 2, dx * width / 2
|
||||
p1 = (c - dx * half, c - dy * half)
|
||||
p2 = (c + dx * half, c + dy * half)
|
||||
d.polygon([(p1[0] + nx, p1[1] + ny), (p2[0] + nx, p2[1] + ny), (p2[0] - nx, p2[1] - ny), (p1[0] - nx, p1[1] - ny)], fill=INK)
|
||||
return im.resize((size, size), Image.LANCZOS)
|
||||
def arms(size):
|
||||
"""The three arms as (centre, unit direction, half-length, half-width) in pixel units."""
|
||||
c = size / 2
|
||||
half, hw = 150 / 512 * size, 29 / 512 * size
|
||||
return [((c, c), (math.cos(math.radians(90 + 60 * k)), math.sin(math.radians(90 + 60 * k))), half, hw) for k in range(3)]
|
||||
|
||||
|
||||
def inside(x, y, shapes):
|
||||
for (cx, cy), (dx, dy), half, hw in shapes:
|
||||
px, py = x - cx, y - cy
|
||||
along, across = px * dx + py * dy, -px * dy + py * dx
|
||||
if abs(along) <= half and abs(across) <= hw:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def asterisk(size):
|
||||
shapes = arms(size)
|
||||
rows = []
|
||||
n = SS * SS
|
||||
for y in range(size):
|
||||
row = bytearray([0]) # PNG filter type 0
|
||||
for x in range(size):
|
||||
hit = 0
|
||||
for sy in range(SS):
|
||||
for sx in range(SS):
|
||||
if inside(x + (sx + 0.5) / SS, y + (sy + 0.5) / SS, shapes):
|
||||
hit += 1
|
||||
t = hit / n
|
||||
row.extend(int(round(g + (i - g) * t)) for g, i in zip(GROUND, INK))
|
||||
rows.append(bytes(row))
|
||||
return b"".join(rows)
|
||||
|
||||
|
||||
def png(size, raw):
|
||||
def chunk(kind, data):
|
||||
return struct.pack(">I", len(data)) + kind + data + struct.pack(">I", zlib.crc32(kind + data) & 0xFFFFFFFF)
|
||||
ihdr = struct.pack(">IIBBBBB", size, size, 8, 2, 0, 0, 0)
|
||||
return b"\x89PNG\r\n\x1a\n" + chunk(b"IHDR", ihdr) + chunk(b"IDAT", zlib.compress(raw, 9)) + chunk(b"IEND", b"")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
out = ROOT / "icons"
|
||||
out.mkdir(exist_ok=True)
|
||||
for name, size in (("icon-512.png", 512), ("icon-192.png", 192), ("apple-touch-icon.png", 180)):
|
||||
asterisk(size).convert("RGB").save(out / name, optimize=True)
|
||||
(out / name).write_bytes(png(size, asterisk(size)))
|
||||
print("built: icons/" + name)
|
||||
Reference in new issue
Block a user