#!/usr/bin/env python3 """Генерация значка SysDevCleaner. Значок рисуется геометрией, а не глифом шрифта: тонкая «палочка» из Nerd Font на 16×16 превращалась в неразборчивое пятно. Здесь метла нарисована жирными примитивами и остаётся узнаваемой даже в самом мелком размере. python make_icon.py Результат — ``sysdevcleaner.ico`` (размеры 16…256) и ``sysdevcleaner.png`` 256×256 для README. """ from __future__ import annotations import os from PIL import Image, ImageDraw ROOT = os.path.dirname(os.path.abspath(__file__)) ICO = os.path.join(ROOT, "sysdevcleaner.ico") PNG = os.path.join(ROOT, "sysdevcleaner.png") SIZES = (16, 24, 32, 48, 64, 128, 256) R = 1024 # рисуем крупно, потом уменьшаем — края мягче BG_TOP = (82, 154, 226) # accent из тёмной темы, чуть светлее BG_BOTTOM = (42, 88, 150) FG = 255 # метла белая, задаётся через маску def gradient(size: int) -> Image.Image: img = Image.new("RGB", (1, size)) d = ImageDraw.Draw(img) for y in range(size): t = y / max(1, size - 1) d.point((0, y), tuple(int(a + (b - a) * t) for a, b in zip(BG_TOP, BG_BOTTOM))) return img.resize((size, size), Image.BICUBIC) def rounded_mask(size: int, radius_ratio: float = 0.22) -> Image.Image: mask = Image.new("L", (size, size), 0) ImageDraw.Draw(mask).rounded_rectangle( [0, 0, size - 1, size - 1], radius=int(size * radius_ratio), fill=255) return mask def broom_mask() -> Image.Image: """Маска метлы: рисуем вертикально, потом поворачиваем. Вертикальная заготовка проще в координатах, а наклон даёт узнаваемый силуэт. """ pad = R // 2 m = Image.new("L", (R + pad, R + pad), 0) d = ImageDraw.Draw(m) cx = (R + pad) // 2 # черенок d.rounded_rectangle([cx - 52, 150, cx + 52, 640], radius=52, fill=FG) # обхват под черенком d.rounded_rectangle([cx - 132, 610, cx + 132, 710], radius=40, fill=FG) # ворс — расширяющаяся трапеция d.polygon([(cx - 118, 700), (cx + 118, 700), (cx + 250, 1030), (cx - 250, 1030)], fill=FG) # прорези в ворсе: режем маску, а не рисуем поверх фона for dx in (-125, 0, 125): d.line([(cx + dx * 0.62, 790), (cx + dx, 1040)], fill=0, width=34) # нижний край чуть скруглим, срезав уголки d.polygon([(cx - 250, 1030), (cx - 250, 1100), (cx + 250, 1100), (cx + 250, 1030)], fill=0) m = m.rotate(-38, resample=Image.BICUBIC, expand=True, center=(cx, (R + pad) // 2)) # вписываем в плитку: обрезаем по содержимому и масштабируем до 66% bbox = m.getbbox() m = m.crop(bbox) target = int(R * 0.66) scale = target / max(m.size) m = m.resize((max(1, int(m.width * scale)), max(1, int(m.height * scale))), Image.LANCZOS) out = Image.new("L", (R, R), 0) out.paste(m, ((R - m.width) // 2, (R - m.height) // 2)) return out def build() -> Image.Image: base = gradient(R).convert("RGBA") base.putalpha(rounded_mask(R)) mask = broom_mask() white = Image.new("RGBA", (R, R), (255, 255, 255, 0)) white.putalpha(mask) return Image.alpha_composite(base, white) def main() -> int: icon = build() icon.resize((256, 256), Image.LANCZOS).save(PNG) print(f" {PNG}") frames = [icon.resize((s, s), Image.LANCZOS) for s in SIZES] frames[-1].save(ICO, format="ICO", sizes=[(s, s) for s in SIZES], append_images=frames[:-1]) print(f" {ICO} (размеры: {', '.join(str(s) for s in SIZES)})") return 0 if __name__ == "__main__": raise SystemExit(main())