on master help sign in
diagonal/scripts/render-icons.py
44 lines 1.7 kB history raw
1#!/usr/bin/env python3
2"""Renders icons/icon.png to the PNG sizes the manifest lists. Needs Pillow (python3 -m pip install pillow).
4The source is the full-size app icon. Its transparent margin is trimmed first so the rounded square fills the
516 and 32 px toolbar icons; the larger sizes keep a little padding, as Chrome's extension page expects.
6"""
7from pathlib import Path
9from PIL import Image, ImageFilter
11ICONS = Path(__file__).resolve().parent.parent / "icons"
12# size -> padding in px on each side at that size
13SIZES = {16: 0, 32: 0, 48: 2, 128: 8}
16def trimmed(src: Image.Image) -> Image.Image:
17 # Near-invisible specks around the shape would otherwise widen the bounding box.
18 solid = src.getchannel("A").point(lambda a: 255 if a > 32 else 0)
19 left, top, right, bottom = solid.getbbox()
20 side = max(right - left, bottom - top)
21 cx, cy = (left + right) / 2, (top + bottom) / 2
22 box = (round(cx - side / 2), round(cy - side / 2), round(cx + side / 2), round(cy + side / 2))
23 return src.crop(box)
26def render(art: Image.Image, size: int, pad: int) -> Image.Image:
27 inner = size - 2 * pad
28 small = art.resize((inner, inner), Image.LANCZOS)
29 if inner <= 32:
30 small = small.filter(ImageFilter.UnsharpMask(radius=0.6, percent=60, threshold=0))
31 out = Image.new("RGBA", (size, size), (0, 0, 0, 0))
32 out.paste(small, (pad, pad))
33 return out
36def main() -> None:
37 art = trimmed(Image.open(ICONS / "icon.png").convert("RGBA"))
38 for size, pad in SIZES.items():
39 render(art, size, pad).save(ICONS / f"{size}.png", optimize=True)
40 print("icons written")
43if __name__ == "__main__":
44 main()