from __future__ import annotations from pathlib import Path from PIL import Image, ImageDraw ROOT = Path(__file__).resolve().parents[1] BRAND_DIR = ROOT / "nouri" / "static" / "brand" BG_TOP = "#F6C394" BG_BOTTOM = "#DE9862" BOWL = "#FFF7EF" STROKE = "#8C533B" def rounded_gradient_icon(size: int, *, maskable: bool = False) -> Image.Image: image = Image.new("RGBA", (size, size), (0, 0, 0, 0)) draw = ImageDraw.Draw(image) radius = int(size * (0.28 if maskable else 0.24)) for y in range(size): blend = y / max(size - 1, 1) r1, g1, b1 = ImageColorTuple(BG_TOP) r2, g2, b2 = ImageColorTuple(BG_BOTTOM) color = ( int(r1 + (r2 - r1) * blend), int(g1 + (g2 - g1) * blend), int(b1 + (b2 - b1) * blend), 255, ) draw.line((0, y, size, y), fill=color) mask = Image.new("L", (size, size), 0) ImageDraw.Draw(mask).rounded_rectangle((0, 0, size - 1, size - 1), radius=radius, fill=255) image.putalpha(mask) inset = int(size * (0.11 if maskable else 0.13)) inner = [inset, inset, size - inset, size - inset] draw.rounded_rectangle(inner, radius=int(radius * 0.84), outline=(255, 255, 255, 54), width=max(2, size // 80)) bowl_top = int(size * 0.24) bowl_left = int(size * 0.27) bowl_right = int(size * 0.73) bowl_bottom = int(size * 0.68) draw.rounded_rectangle( (bowl_left, bowl_top, bowl_right, bowl_bottom), radius=int(size * 0.16), fill=BOWL, ) draw.rounded_rectangle( (int(size * 0.31), int(size * 0.31), int(size * 0.69), int(size * 0.60)), radius=int(size * 0.12), fill=(239, 195, 159, 64), ) bowl_curve_top = int(size * 0.56) draw.pieslice( (int(size * 0.24), bowl_curve_top, int(size * 0.76), int(size * 0.84)), start=0, end=180, fill=(247, 179, 125, 255), ) line_width = max(4, size // 26) steam = [ (0.50, 0.31), (0.56, 0.31), (0.56, 0.40), (0.45, 0.50), (0.45, 0.58), ] draw.line([(int(size * x), int(size * y)) for x, y in steam], fill=STROKE, width=line_width, joint="curve") draw.line( [(int(size * 0.45), int(size * 0.58)), (int(size * 0.56), int(size * 0.58))], fill=STROKE, width=line_width, ) return image def ImageColorTuple(hex_color: str) -> tuple[int, int, int]: hex_color = hex_color.lstrip("#") return tuple(int(hex_color[index:index + 2], 16) for index in (0, 2, 4)) def badge_icon(size: int) -> Image.Image: image = Image.new("RGBA", (size, size), (0, 0, 0, 0)) draw = ImageDraw.Draw(image) draw.rounded_rectangle((0, 0, size - 1, size - 1), radius=size // 3, fill=BG_BOTTOM) draw.ellipse((size * 0.18, size * 0.18, size * 0.82, size * 0.82), fill=BOWL) draw.rectangle((size * 0.34, size * 0.52, size * 0.66, size * 0.63), fill=STROKE) return image def save_assets() -> None: BRAND_DIR.mkdir(parents=True, exist_ok=True) rounded_gradient_icon(180).save(BRAND_DIR / "pwa-180.png") rounded_gradient_icon(192).save(BRAND_DIR / "pwa-192.png") rounded_gradient_icon(512).save(BRAND_DIR / "pwa-512.png") rounded_gradient_icon(512, maskable=True).save(BRAND_DIR / "pwa-maskable-512.png") badge_icon(96).save(BRAND_DIR / "pwa-badge.png") if __name__ == "__main__": save_assets()