-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmake_icon.py
More file actions
103 lines (86 loc) · 3.09 KB
/
Copy pathmake_icon.py
File metadata and controls
103 lines (86 loc) · 3.09 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
"""Generates icon.ico (16/24/32/48/64 px) with no third-party dependencies."""
import os
import math
import struct
FACE = (0x1f, 0x25, 0x33)
RING = (0x3d, 0xdc, 0x84)
HAND = (0xe8, 0xec, 0xf4)
PIP = (0x4d, 0xa3, 0xff)
SS = 4 # supersampling factor
def dist_to_segment(px, py, x1, y1, x2, y2):
dx, dy = x2 - x1, y2 - y1
if dx == 0 and dy == 0:
return math.hypot(px - x1, py - y1)
t = max(0.0, min(1.0, ((px - x1) * dx + (py - y1) * dy) / (dx * dx + dy * dy)))
return math.hypot(px - (x1 + t * dx), py - (y1 + t * dy))
def render(size):
"""Return BGRA rows (top-down) for one clock-face image."""
n = size * SS
c = n / 2.0
r_out = n * 0.47
r_in = r_out * 0.80
hand_w = max(1.4, n * 0.045)
# hour hand -> 10 o'clock, minute hand -> 2 o'clock
def polar(angle_deg, length):
a = math.radians(angle_deg - 90)
return c + math.cos(a) * length, c + math.sin(a) * length
hx, hy = polar(300, r_in * 0.52)
mx, my = polar(60, r_in * 0.74)
acc = [[[0, 0, 0, 0] for _ in range(size)] for _ in range(size)]
for yy in range(n):
for xx in range(n):
px, py = xx + 0.5, yy + 0.5
d = math.hypot(px - c, py - c)
col = None
if d <= r_out:
col = RING if d > r_in else FACE
if d <= r_in:
if (dist_to_segment(px, py, c, c, hx, hy) <= hand_w
or dist_to_segment(px, py, c, c, mx, my) <= hand_w):
col = HAND
elif d <= hand_w * 1.5:
col = PIP
if col:
cell = acc[yy // SS][xx // SS]
cell[0] += col[0]
cell[1] += col[1]
cell[2] += col[2]
cell[3] += 255
rows = []
total = SS * SS
for y in range(size):
row = bytearray()
for x in range(size):
rr, gg, bb, aa = acc[y][x]
if aa == 0:
row += b"\x00\x00\x00\x00"
continue
hits = aa // 255
row += bytes((bb // hits, gg // hits, rr // hits, aa // total))
rows.append(bytes(row))
return rows
def ico_image(size):
rows = render(size)
xor = b"".join(reversed(rows)) # DIB is bottom-up
mask_stride = ((size + 31) // 32) * 4
and_mask = b"\x00" * (mask_stride * size)
header = struct.pack(
"<IiiHHIIiiII", 40, size, size * 2, 1, 32, 0, len(xor) + len(and_mask),
0, 0, 0, 0,
)
return header + xor + and_mask
def main():
sizes = [16, 24, 32, 48, 64]
images = [ico_image(s) for s in sizes]
out = struct.pack("<HHH", 0, 1, len(sizes))
offset = 6 + 16 * len(sizes)
for s, img in zip(sizes, images):
out += struct.pack("<BBBBHHII", s % 256, s % 256, 0, 0, 1, 32, len(img), offset)
offset += len(img)
out += b"".join(images)
path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "icon.ico")
with open(path, "wb") as f:
f.write(out)
print(f"wrote {path} ({len(out):,} bytes, sizes {sizes})")
if __name__ == "__main__":
main()