#!/usr/bin/env python3 """Generate HART Mobile app icon with smooth FSK waveform.""" import math from PIL import Image, ImageDraw SIZES = { "mdpi": 48, "hdpi": 72, "xhdpi": 96, "xxhdpi": 144, "xxxhdpi": 192, } BG_COLOR = (25, 82, 148) WAVE_COLOR = (255, 255, 255) CORNER_RADIUS_RATIO = 0.22 def fsk_points(s, num=2000): """Generate centerline points of FSK waveform on canvas size s.""" margin_x = s * 0.10 margin_y = s * 0.28 w = s - 2 * margin_x cy = s / 2 amplitude = (s - 2 * margin_y) * 0.45 cycles_fast = 3.0 cycles_slow = 1.5 transition = cycles_fast / (cycles_fast + cycles_slow * 2) pts = [] for i in range(num + 1): t = i / num x = margin_x + t * w if t <= transition: phase = 2 * math.pi * cycles_fast * (t / transition) else: local_t = (t - transition) / (1 - transition) phase = 2 * math.pi * cycles_fast + 2 * math.pi * cycles_slow * local_t y = cy - amplitude * math.sin(phase) pts.append((x, y)) return pts def thick_curve_polygon(pts, thickness): """Build a filled polygon representing a thick smooth curve.""" half = thickness / 2.0 upper = [] lower = [] for i in range(len(pts)): # Compute tangent direction if i == 0: dx, dy = pts[1][0] - pts[0][0], pts[1][1] - pts[0][1] elif i == len(pts) - 1: dx, dy = pts[-1][0] - pts[-2][0], pts[-1][1] - pts[-2][1] else: dx, dy = pts[i + 1][0] - pts[i - 1][0], pts[i + 1][1] - pts[i - 1][1] length = math.sqrt(dx * dx + dy * dy) if length < 1e-9: nx, ny = 0, 1 else: nx, ny = -dy / length, dx / length x, y = pts[i] upper.append((x + nx * half, y + ny * half)) lower.append((x - nx * half, y - ny * half)) # Polygon: upper forward + lower backward return upper + lower[::-1] def generate_icon(size, round_mask=False): scale = 4 s = size * scale img = Image.new("RGBA", (s, s), (0, 0, 0, 0)) draw = ImageDraw.Draw(img) radius = int(s * CORNER_RADIUS_RATIO) draw.rounded_rectangle([0, 0, s - 1, s - 1], radius=radius, fill=BG_COLOR) pts = fsk_points(s) thickness = s * 0.055 poly = thick_curve_polygon(pts, thickness) draw.polygon(poly, fill=WAVE_COLOR) # Round the endpoints with circles for p in [pts[0], pts[-1]]: r = thickness / 2 draw.ellipse([p[0] - r, p[1] - r, p[0] + r, p[1] + r], fill=WAVE_COLOR) img = img.resize((size, size), Image.LANCZOS) if round_mask: mask = Image.new("L", (size, size), 0) ImageDraw.Draw(mask).ellipse([0, 0, size - 1, size - 1], fill=255) img.putalpha(mask) return img def main(): base = "app/src/main/res" for density, size in SIZES.items(): icon = generate_icon(size) icon.save(f"{base}/mipmap-{density}/ic_launcher.png") icon_round = generate_icon(size, round_mask=True) icon_round.save(f"{base}/mipmap-{density}/ic_launcher_round.png") print(f" mipmap-{density}: {size}x{size}") store = generate_icon(512) store.save(f"{base}/mipmap-xxxhdpi/store_icon_512.png") print(" store_icon_512.png (512x512)") if __name__ == "__main__": main()