packaging/make_icon.py
Rama main · réplica de lectura del repositorio original.
"""
Generate STT application icons in all required formats.
Outputs:
icon.png — 512x512 source (Linux / general)
icon.ico — multi-resolution Windows icon
icon.icns — macOS icon bundle (requires macOS + iconutil)
Run before PyInstaller:
python make_icon.py
"""
import os
import shutil
import subprocess
import sys
from PIL import Image, ImageDraw
# ── Design constants ──────────────────────────────────────────────────────────
# Pillow port of packaging/icon-source.svg (256×256 viewBox); coordinates below
# are in that viewBox space, scaled by `u`. Keep the two files in sync.
BG_COLOR = (243, 242, 242) # light #f3f2f2
FG_COLOR = (32, 30, 29) # dark #201e1d
ACCENT_COLOR = (236, 48, 19) # red #ec3013
SUPERSAMPLE = 4 # render N× then LANCZOS-downscale so 16-32px sizes stay crisp
def draw_icon(size: int) -> Image.Image:
s = size * SUPERSAMPLE
u = s / 256 # SVG viewBox unit
img = Image.new("RGBA", (s, s), (0, 0, 0, 0))
draw = ImageDraw.Draw(img)
lw = 12 * u # SVG stroke-width
# Rounded-square background plate (the SVG's square bg, platform-styled)
pad = s / 16
draw.rounded_rectangle(
[pad, pad, s - pad, s - pad],
radius=s / 5,
fill=BG_COLOR,
)
# ── Microphone body (rounded-rect outline) ───────────────────────────────
draw.rounded_rectangle(
[90 * u, 34 * u, 166 * u, 156 * u],
radius=38 * u,
outline=FG_COLOR,
width=round(lw),
)
# ── Level bars ───────────────────────────────────────────────────────────
for x, y, w, h in [(106, 80, 9, 30), (123, 66, 9, 58), (140, 80, 9, 30)]:
draw.rectangle([x * u, y * u, (x + w) * u, (y + h) * u], fill=ACCENT_COLOR)
# ── Stand: two verticals + bottom semicircle (stroke centred on path) ────
# Verticals at x=72 and x=184, y 132→142, square caps extending 6 up.
for x in (72, 184):
draw.rectangle(
[(x - 6) * u, (132 - 6) * u, (x + 6) * u, 142 * u],
fill=FG_COLOR,
)
# Semicircle: centreline radius 56 around (128, 142); bbox is outer edge.
draw.arc(
[(128 - 62) * u, (142 - 62) * u, (128 + 62) * u, (142 + 62) * u],
start=0, end=180,
fill=FG_COLOR,
width=round(lw),
)
# ── Stem and base ────────────────────────────────────────────────────────
draw.rectangle([121 * u, 198 * u, 135 * u, 220 * u], fill=FG_COLOR)
draw.rectangle([94 * u, 216 * u, 162 * u, 229 * u], fill=FG_COLOR)
return img.resize((size, size), Image.LANCZOS)
# ── Export helpers ────────────────────────────────────────────────────────────
ICO_SIZES = [16, 24, 32, 48, 64, 128, 256]
ICNS_SIZES = {
"icon_16x16.png": 16,
"icon_16x16@2x.png": 32,
"icon_32x32.png": 32,
"icon_32x32@2x.png": 64,
"icon_128x128.png": 128,
"icon_128x128@2x.png": 256,
"icon_256x256.png": 256,
"icon_256x256@2x.png": 512,
"icon_512x512.png": 512,
"icon_512x512@2x.png": 1024,
}
def make_ico(path: str = "icon.ico"):
images = [draw_icon(s).convert("RGBA") for s in ICO_SIZES]
# Save from the LARGEST frame: Pillow silently drops any requested size
# bigger than the base image, so saving from 16px yields a 16px-only .ico.
images[-1].save(
path, format="ICO",
sizes=[(s, s) for s in ICO_SIZES],
append_images=images[:-1],
)
print(f" {path}")
def make_png(path: str = "icon.png", size: int = 512):
draw_icon(size).save(path, format="PNG")
print(f" {path}")
def make_icns(path: str = "icon.icns"):
iconset = "icon.iconset"
os.makedirs(iconset, exist_ok=True)
for name, sz in ICNS_SIZES.items():
draw_icon(sz).save(os.path.join(iconset, name), format="PNG")
try:
subprocess.run(
["iconutil", "-c", "icns", iconset, "-o", path],
check=True, capture_output=True,
)
print(f" {path}")
except FileNotFoundError:
print(" icon.icns skipped (iconutil not available — macOS only)")
finally:
shutil.rmtree(iconset, ignore_errors=True)
# ── Main ──────────────────────────────────────────────────────────────────────
if __name__ == "__main__":
print("Generating icons...")
make_ico()
make_png()
if sys.platform == "darwin":
make_icns()
print("Done.")