update
This commit is contained in:
205
APP/spotify-recorder/create_icon.py
Normal file
205
APP/spotify-recorder/create_icon.py
Normal file
@@ -0,0 +1,205 @@
|
||||
"""Generate the app icon: green rounded square with a modern music note."""
|
||||
import struct
|
||||
import zlib
|
||||
import os
|
||||
|
||||
def create_icon():
|
||||
sizes = [16, 32, 48, 64, 128, 256]
|
||||
images = []
|
||||
for s in sizes:
|
||||
images.append(_render(s))
|
||||
_write_ico(images, sizes,
|
||||
os.path.join(os.path.dirname(__file__), "app.ico"))
|
||||
print("app.ico created!")
|
||||
|
||||
|
||||
def _render(size):
|
||||
"""Render a green rounded-square with a music note as RGBA pixels."""
|
||||
pixels = bytearray(size * size * 4)
|
||||
cx, cy = size / 2, size / 2
|
||||
r = size * 0.42
|
||||
corner = size * 0.22
|
||||
|
||||
for y in range(size):
|
||||
for x in range(size):
|
||||
i = (y * size + x) * 4
|
||||
dx = x - cx
|
||||
dy = y - cy
|
||||
|
||||
inside = _rounded_rect(x, y, size, corner)
|
||||
|
||||
if inside >= 1.0:
|
||||
nr, ng, nb, na = _note_pixel(x, y, size)
|
||||
if na > 0:
|
||||
pixels[i] = nr
|
||||
pixels[i+1] = ng
|
||||
pixels[i+2] = nb
|
||||
pixels[i+3] = na
|
||||
else:
|
||||
pixels[i] = 29
|
||||
pixels[i+1] = 185
|
||||
pixels[i+2] = 84
|
||||
pixels[i+3] = 255
|
||||
elif inside > 0:
|
||||
a = int(inside * 255)
|
||||
nr, ng, nb, na = _note_pixel(x, y, size)
|
||||
if na > 0:
|
||||
pixels[i] = nr
|
||||
pixels[i+1] = ng
|
||||
pixels[i+2] = nb
|
||||
pixels[i+3] = min(255, int(na * inside))
|
||||
else:
|
||||
pixels[i] = 29
|
||||
pixels[i+1] = 185
|
||||
pixels[i+2] = 84
|
||||
pixels[i+3] = a
|
||||
else:
|
||||
pixels[i:i+4] = b'\x00\x00\x00\x00'
|
||||
|
||||
return bytes(pixels)
|
||||
|
||||
|
||||
def _rounded_rect(x, y, size, corner):
|
||||
"""Return 0.0-1.0 coverage for anti-aliased rounded rectangle."""
|
||||
margin = size * 0.06
|
||||
left, top = margin, margin
|
||||
right, bottom = size - margin - 1, size - margin - 1
|
||||
|
||||
if x < left or x > right or y < top or y > bottom:
|
||||
return 0.0
|
||||
|
||||
cr = corner
|
||||
corners = [
|
||||
(left + cr, top + cr),
|
||||
(right - cr, top + cr),
|
||||
(left + cr, bottom - cr),
|
||||
(right - cr, bottom - cr),
|
||||
]
|
||||
|
||||
for ccx, ccy in corners:
|
||||
in_corner_x = (x < ccx and ccx == corners[0][0] or
|
||||
x < ccx and ccx == corners[2][0] or
|
||||
x > ccx and ccx == corners[1][0] or
|
||||
x > ccx and ccx == corners[3][0])
|
||||
in_corner_y = (y < ccy and ccy == corners[0][1] or
|
||||
y < ccy and ccy == corners[1][1] or
|
||||
y > ccy and ccy == corners[2][1] or
|
||||
y > ccy and ccy == corners[3][1])
|
||||
if in_corner_x and in_corner_y:
|
||||
dist = ((x - ccx) ** 2 + (y - ccy) ** 2) ** 0.5
|
||||
if dist > cr + 1:
|
||||
return 0.0
|
||||
elif dist > cr - 1:
|
||||
return max(0.0, min(1.0, cr + 1 - dist))
|
||||
else:
|
||||
return 1.0
|
||||
|
||||
return 1.0
|
||||
|
||||
|
||||
def _note_pixel(x, y, size):
|
||||
"""Draw a modern double-beamed music note (two note heads + beam)."""
|
||||
s = size
|
||||
white = (255, 255, 255, 240)
|
||||
|
||||
head1_cx = s * 0.33
|
||||
head1_cy = s * 0.68
|
||||
head2_cx = s * 0.60
|
||||
head2_cy = s * 0.75
|
||||
|
||||
head_rx = s * 0.10
|
||||
head_ry = s * 0.07
|
||||
|
||||
dx1 = (x - head1_cx) / head_rx
|
||||
dy1 = (y - head1_cy) / head_ry
|
||||
if dx1 * dx1 + dy1 * dy1 <= 1.0:
|
||||
return white
|
||||
|
||||
dx2 = (x - head2_cx) / head_rx
|
||||
dy2 = (y - head2_cy) / head_ry
|
||||
if dx2 * dx2 + dy2 * dy2 <= 1.0:
|
||||
return white
|
||||
|
||||
stem_w = max(1, s * 0.04)
|
||||
stem1_x = head1_cx + head_rx - stem_w / 2
|
||||
stem2_x = head2_cx + head_rx - stem_w / 2
|
||||
|
||||
stem_top = s * 0.22
|
||||
stem1_bot = head1_cy
|
||||
stem2_bot = head2_cy
|
||||
|
||||
if (abs(x - stem1_x) <= stem_w and stem_top <= y <= stem1_bot):
|
||||
return white
|
||||
if (abs(x - stem2_x) <= stem_w and stem_top + s * 0.07 <= y <= stem2_bot):
|
||||
return white
|
||||
|
||||
beam_h = max(1, s * 0.045)
|
||||
for bi, beam_y_base in enumerate([stem_top, stem_top + beam_h * 2.5]):
|
||||
left_x = stem1_x
|
||||
right_x = stem2_x
|
||||
left_y = beam_y_base
|
||||
right_y = beam_y_base + s * 0.07
|
||||
|
||||
if left_x <= x <= right_x:
|
||||
t = (x - left_x) / max(1, right_x - left_x)
|
||||
beam_top = left_y + t * (right_y - left_y)
|
||||
beam_bot = beam_top + beam_h
|
||||
if beam_top <= y <= beam_bot:
|
||||
return white
|
||||
|
||||
return (0, 0, 0, 0)
|
||||
|
||||
|
||||
def _make_png(rgba_data, width, height):
|
||||
"""Create a minimal PNG from RGBA pixel data."""
|
||||
def chunk(ctype, data):
|
||||
c = ctype + data
|
||||
crc = struct.pack('>I', zlib.crc32(c) & 0xffffffff)
|
||||
return struct.pack('>I', len(data)) + c + crc
|
||||
|
||||
raw = b''
|
||||
for row in range(height):
|
||||
raw += b'\x00'
|
||||
off = row * width * 4
|
||||
raw += rgba_data[off:off + width * 4]
|
||||
|
||||
ihdr = struct.pack('>IIBBBBB', width, height, 8, 6, 0, 0, 0)
|
||||
compressed = zlib.compress(raw)
|
||||
|
||||
png = b'\x89PNG\r\n\x1a\n'
|
||||
png += chunk(b'IHDR', ihdr)
|
||||
png += chunk(b'IDAT', compressed)
|
||||
png += chunk(b'IEND', b'')
|
||||
return png
|
||||
|
||||
|
||||
def _write_ico(images, sizes, path):
|
||||
"""Write a multi-size .ico file."""
|
||||
count = len(sizes)
|
||||
header = struct.pack('<HHH', 0, 1, count)
|
||||
|
||||
dir_size = 6 + count * 16
|
||||
offset = dir_size
|
||||
entries = []
|
||||
png_datas = []
|
||||
|
||||
for idx, s in enumerate(sizes):
|
||||
png = _make_png(images[idx], s, s)
|
||||
png_datas.append(png)
|
||||
w = 0 if s >= 256 else s
|
||||
h = 0 if s >= 256 else s
|
||||
entry = struct.pack('<BBBBHHIH',
|
||||
w, h, 0, 0, 1, 32, len(png), offset)
|
||||
entries.append(entry)
|
||||
offset += len(png)
|
||||
|
||||
with open(path, 'wb') as f:
|
||||
f.write(header)
|
||||
for e in entries:
|
||||
f.write(e)
|
||||
for p in png_datas:
|
||||
f.write(p)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
create_icon()
|
||||
Reference in New Issue
Block a user