diff options
Diffstat (limited to 'tools/make_icon.py')
| -rw-r--r-- | tools/make_icon.py | 68 |
1 files changed, 68 insertions, 0 deletions
diff --git a/tools/make_icon.py b/tools/make_icon.py new file mode 100644 index 0000000..6bb1cbf --- /dev/null +++ b/tools/make_icon.py @@ -0,0 +1,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() |
