summaryrefslogtreecommitdiff
path: root/tools/make_icon.py
blob: 6bb1cbfe23c3855186b661b055761526abbc3407 (plain) (blame)
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
#!/usr/bin/env python3
"""Draw the app's launcher icon: a white padlock with three "code" dots on a
teal disc, on a transparent background.

The icon is drawn at 8x the target size and scaled down, which gives smooth
edges at the small sizes watches use (35 to 70 px).

Usage:  make_icon.py --size 60 --out resources/drawables/launcher_icon.png
"""

from __future__ import annotations

import argparse
from pathlib import Path

from PIL import Image, ImageDraw

TEAL = (14, 124, 134, 255)
WHITE = (255, 255, 255, 255)
SUPERSAMPLE = 8


def draw_icon(size: int) -> Image.Image:
    """Return the icon as an RGBA image of size x size pixels."""
    s = size * SUPERSAMPLE
    img = Image.new("RGBA", (s, s), (0, 0, 0, 0))
    d = ImageDraw.Draw(img)

    margin = s * 0.02
    d.ellipse((margin, margin, s - margin, s - margin), fill=TEAL)

    # Lock body: a rounded rectangle in the lower middle.
    body_w, body_h = s * 0.50, s * 0.36
    body_x0, body_y0 = (s - body_w) / 2, s * 0.44
    d.rounded_rectangle((body_x0, body_y0, body_x0 + body_w, body_y0 + body_h), radius=s * 0.06, fill=WHITE)

    # Shackle: the top half of an ellipse plus two short legs into the body.
    stroke = int(s * 0.07)
    sx0, sx1, sy0, sy1 = s * 0.34, s * 0.66, s * 0.20, s * 0.56
    d.arc((sx0, sy0, sx1, sy1), start=180, end=360, fill=WHITE, width=stroke)
    leg_top, leg_bottom = (sy0 + sy1) / 2, body_y0 + s * 0.02
    d.rectangle((sx0, leg_top, sx0 + stroke, leg_bottom), fill=WHITE)
    d.rectangle((sx1 - stroke, leg_top, sx1, leg_bottom), fill=WHITE)

    # Three dots on the body stand for a one-time code.
    radius = s * 0.035
    center_y = body_y0 + body_h * 0.5
    for i in range(3):
        center_x = s * (0.38 + 0.12 * i)
        d.ellipse((center_x - radius, center_y - radius, center_x + radius, center_y + radius), fill=TEAL)

    return img.resize((size, size), Image.Resampling.LANCZOS)


def main() -> None:
    parser = argparse.ArgumentParser(description=__doc__.splitlines()[0])
    parser.add_argument("--size", type=int, default=60, help="icon width and height in pixels")
    parser.add_argument("--out", type=Path, required=True, help="PNG file to write")
    args = parser.parse_args()
    if not 16 <= args.size <= 256:
        parser.error("--size must be between 16 and 256")
    args.out.parent.mkdir(parents=True, exist_ok=True)
    draw_icon(args.size).save(args.out, format="PNG", optimize=True)
    print(f"wrote {args.out} ({args.size}x{args.size})")


if __name__ == "__main__":
    main()