summaryrefslogtreecommitdiff
path: root/tools/make_icon.py
diff options
context:
space:
mode:
authorcirakg <cirakg@cirakg.xyz>2026-10-18 20:28:09 +0200
committercirakg <cirakg@cirakg.xyz>2026-10-18 20:28:09 +0200
commit2071e2beda763137416ac386d4bb953711ec995d (patch)
treebdcf840cb457f8286c1d967410f98972eb99b50a /tools/make_icon.py
parenta9b1dc8bd9354973eabd72f56d82b2eab6f48d56 (diff)
test(tools): add test-log judge, docs and policy checks with tests
- ciq_test_report.py: decides pass/fail from Run No Evil output instead of monkeydo's unreliable exit code; writes JUnit XML. - check_docs.py: offline check of relative links and GitHub anchors. - lint_project.py: project rules (headers, no logging outside Diag, no secret-like literals, no network APIs or permissions). - check_budgets.py with budgets.json: perf and memory gates. - make_icon.py: reproducible launcher icon. - 31 pytest tests, including Hypothesis fuzzing of the log parser and integration checks against the real docs and sources; ruff and mypy (strict) clean. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Diffstat (limited to 'tools/make_icon.py')
-rw-r--r--tools/make_icon.py68
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()