#!/usr/bin/env python3 """Generate the cmshopee application logo. Geometry lives here once and is emitted to both the editable SVG master and the rasterised Windows icon, so the two can never drift apart. Usage: py -3.10 scripts/gen_logo.py Outputs (all under app/assets/): cmshopee-logo.svg editable master cmshopee.ico multi-size Windows icon (exe + window icon) cmshopee-256.png preview / non-Windows fallback The mark is an upward arrow rising from a base bar: the product listing being lifted and published. It deliberately avoids Shopee's own bag glyph so the tool is not mistaken for an official Shopee application. """ from __future__ import annotations import os import sys try: from PIL import Image, ImageDraw except ImportError: # pragma: no cover - developer tooling only sys.exit("需要 Pillow 才能生成图标:py -3.10 -m pip install Pillow") CANVAS = 256 SUPERSAMPLE = 8 ICO_SIZES = (16, 20, 24, 32, 40, 48, 64, 128, 256) # Badge BADGE = (10, 10, 246, 246) # x0, y0, x1, y1 BADGE_RADIUS = 54 GRADIENT_FROM = (245, 121, 63) # #F5793F GRADIENT_TO = (214, 55, 28) # #D6371C # Mark (white): an arrow lifting a listing off its base, plus an AI sparkle. ARROW_HEAD = ((112, 58), (56, 116), (168, 116)) ARROW_STEM = (90, 110, 134, 162) # x0, y0, x1, y1 ARROW_STEM_RADIUS = 10 BASE_BAR = (56, 178, 168, 204) BASE_BAR_RADIUS = 13 MARK_COLOR = (255, 255, 255) # Four-point sparkle: the AI generation step. Degrades to a small accent dot # at 16px rather than turning to mush. SPARKLE_CENTER = (194, 74) SPARKLE_OUTER = 34 SPARKLE_INNER = 11 ASSETS_DIR = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "app", "assets") def _hex(rgb): return "#%02X%02X%02X" % rgb def _gradient(size): """Diagonal linear gradient, built small and scaled up (it is smooth).""" base = Image.new("RGB", (CANVAS, CANVAS)) pixels = base.load() span = 2 * (CANVAS - 1) for y in range(CANVAS): for x in range(CANVAS): t = (x + y) / span pixels[x, y] = tuple( round(GRADIENT_FROM[i] + (GRADIENT_TO[i] - GRADIENT_FROM[i]) * t) for i in range(3) ) return base.resize((size, size), Image.LANCZOS) def _scaled(values, factor): return [value * factor for value in values] def _sparkle_points(): """Eight alternating outer/inner points forming a four-point star.""" cx, cy = SPARKLE_CENTER o, i = SPARKLE_OUTER, SPARKLE_INNER diagonal = i * (2 ** 0.5) / 2 return [ (cx, cy - o), (cx + diagonal, cy - diagonal), (cx + o, cy), (cx + diagonal, cy + diagonal), (cx, cy + o), (cx - diagonal, cy + diagonal), (cx - o, cy), (cx - diagonal, cy - diagonal), ] def render(size): """Render one square icon at the given pixel size.""" work = size * SUPERSAMPLE factor = work / CANVAS badge_mask = Image.new("L", (work, work), 0) draw = ImageDraw.Draw(badge_mask) draw.rounded_rectangle(_scaled(BADGE, factor), radius=BADGE_RADIUS * factor, fill=255) mark_mask = Image.new("L", (work, work), 0) draw = ImageDraw.Draw(mark_mask) draw.polygon([(x * factor, y * factor) for x, y in ARROW_HEAD], fill=255) draw.rounded_rectangle(_scaled(ARROW_STEM, factor), radius=ARROW_STEM_RADIUS * factor, fill=255) draw.rounded_rectangle(_scaled(BASE_BAR, factor), radius=BASE_BAR_RADIUS * factor, fill=255) draw.polygon([(x * factor, y * factor) for x, y in _sparkle_points()], fill=255) icon = Image.new("RGBA", (work, work), (0, 0, 0, 0)) icon.paste(_gradient(work), (0, 0), badge_mask) icon.paste(Image.new("RGBA", (work, work), MARK_COLOR + (255,)), (0, 0), mark_mask) return icon.resize((size, size), Image.LANCZOS) def write_svg(path): head = "M {} {} L {} {} L {} {} Z".format(*[c for point in ARROW_HEAD for c in point]) sparkle = " ".join(f"{x:.1f},{y:.1f}" for x, y in _sparkle_points()) svg = f""" 蝦皮圈優化助手 """ with open(path, "w", encoding="utf-8") as fh: fh.write(svg) def main(): os.makedirs(ASSETS_DIR, exist_ok=True) # Each size is rendered from the vector geometry rather than downscaled from # one bitmap, so 16px and 20px stay crisp in Explorer and the taskbar. images = [render(size) for size in ICO_SIZES] largest = images[-1] ico_path = os.path.join(ASSETS_DIR, "cmshopee.ico") largest.save( ico_path, format="ICO", sizes=[(size, size) for size in ICO_SIZES], append_images=images[:-1], ) png_path = os.path.join(ASSETS_DIR, "cmshopee-256.png") largest.save(png_path, format="PNG") svg_path = os.path.join(ASSETS_DIR, "cmshopee-logo.svg") write_svg(svg_path) for path in (svg_path, ico_path, png_path): print(f"已生成 {os.path.relpath(path, os.path.dirname(ASSETS_DIR))} {os.path.getsize(path)} bytes") if __name__ == "__main__": main()