from pathlib import Path from PIL import Image, ImageDraw, ImageFont ROOT = Path(__file__).resolve().parents[1] PUBLIC = ROOT / "public" ASSETS = PUBLIC / "assets" EXT_ICONS = ROOT / "extension" / "icons" JADE_DARK = "#073C31" JADE = "#0D6A54" TURQUOISE = "#249B90" PAPER = "#FFFDF5" PAPER_GREEN = "#E8F6EF" BRASS = "#A56F18" BRASS_LIGHT = "#FFE6A1" CINNABAR = "#C9544A" MUTED = "#65766A" SERIF = "/System/Library/Fonts/Supplemental/Songti.ttc" SANS = "/Users/wangyuqiang/Library/Fonts/SourceHanSansCN-Regular.otf" SANS_HEAVY = "/Users/wangyuqiang/Library/Fonts/SourceHanSansCN-Heavy.otf" def font(path, size): return ImageFont.truetype(path, size) def rounded(draw, xy, radius, fill, outline=None, width=1): draw.rounded_rectangle(xy, radius=radius, fill=fill, outline=outline, width=width) def draw_mark(size=512): scale = size / 256 image = Image.new("RGBA", (size, size), (0, 0, 0, 0)) draw = ImageDraw.Draw(image) rounded(draw, (0, 0, size - 1, size - 1), int(56 * scale), PAPER) rounded(draw, (int(12 * scale), int(12 * scale), int(244 * scale), int(244 * scale)), int(48 * scale), JADE) rounded( draw, (int(25 * scale), int(25 * scale), int(231 * scale), int(231 * scale)), int(38 * scale), None, "#2E8F7D", max(1, int(2 * scale)), ) stroke = max(8, int(13 * scale)) corner = "#FFF8E8" segments = [ ((57, 98), (57, 70), (70, 57), (98, 57)), ((158, 57), (186, 57), (199, 70), (199, 98)), ((199, 158), (199, 186), (186, 199), (158, 199)), ((98, 199), (70, 199), (57, 186), (57, 158)), ] for points in segments: scaled = [(int(x * scale), int(y * scale)) for x, y in points] draw.line(scaled, fill=corner, width=stroke, joint="curve") diamond = [(128, 72), (184, 128), (128, 184), (72, 128)] draw.polygon([(int(x * scale), int(y * scale)) for x, y in diamond], fill=BRASS_LIGHT) inner = [(128, 91), (165, 128), (128, 165), (91, 128)] draw.polygon([(int(x * scale), int(y * scale)) for x, y in inner], fill="#FFF8E8") draw.ellipse( (int(111 * scale), int(111 * scale), int(145 * scale), int(145 * scale)), fill=JADE, ) draw.ellipse( (int(162 * scale), int(70 * scale), int(186 * scale), int(94 * scale)), fill=CINNABAR, outline="#FFF8E8", width=max(3, int(5 * scale)), ) return image def center_text(draw, xy, text, font_obj, fill): x1, y1, x2, y2 = xy box = draw.textbbox((0, 0), text, font=font_obj) width = box[2] - box[0] height = box[3] - box[1] draw.text((x1 + (x2 - x1 - width) / 2, y1 + (y2 - y1 - height) / 2 - box[1]), text, font=font_obj, fill=fill) def draw_share_card(): image = Image.new("RGB", (1200, 630), "#F8FFF9") draw = ImageDraw.Draw(image) for y in range(630): ratio = y / 629 r = int(248 * (1 - ratio) + 255 * ratio) g = int(255 * (1 - ratio) + 245 * ratio) b = int(249 * (1 - ratio) + 222 * ratio) draw.line((0, y, 1200, y), fill=(r, g, b)) for x in range(0, 1200, 32): draw.line((x, 0, x, 630), fill="#D8E8DE", width=1) for y in range(0, 630, 32): draw.line((0, y, 1200, y), fill="#D8E8DE", width=1) draw.ellipse((860, -228, 1380, 292), fill="#F3DE9E") draw.ellipse((-210, 360, 290, 860), fill="#DDF2EF") mark = draw_mark(208) image.paste(mark, (66, 62), mark) title_font = font(SERIF, 64) subtitle_font = font(SANS_HEAVY, 30) body_font = font(SANS, 22) small_font = font(SANS_HEAVY, 17) label_font = font(SANS, 15) draw.text((66, 306), "选品采购台", font=title_font, fill=JADE_DARK) draw.text((69, 394), "从商品机会,到放心采购", font=subtitle_font, fill="#315F55") draw.text((69, 452), "采集竞品 · 匹配货源 · 利润测算 · 拿样决策", font=body_font, fill=MUTED) rounded(draw, (660, 88, 1124, 540), 28, PAPER, "#B9D5C7", 2) draw.text((694, 132), "PRODUCT OPPORTUNITY FLOW", font=small_font, fill=MUTED) cards = [ ((694, 174, 1092, 252), "#E2F3EE", "#B9D5C7", JADE, "发现高潜商品", "淘宝 / 小红书 / 更多平台线索"), ((694, 290, 1092, 368), "#FFF3CB", "#D9BD79", BRASS, "匹配优质货源", "1688 厂家 / 价格 / 保障"), ((694, 406, 1092, 502), JADE, JADE, PAPER, "形成采购决策", "筛选 / 利润 / 风险 / 拿样"), ] for index, (box, fill, outline, accent, title, subtitle) in enumerate(cards): rounded(draw, box, 14, fill, outline, 1) x1, y1, x2, y2 = box if index == 0: draw.ellipse((x1 + 20, y1 + 19, x1 + 60, y1 + 59), fill=JADE) center_text(draw, (x1 + 20, y1 + 19, x1 + 60, y1 + 59), "+", font(SANS, 29), PAPER) text_fill = JADE_DARK sub_fill = MUTED elif index == 1: draw.rectangle((x1 + 22, y1 + 25, x1 + 61, y1 + 55), fill=BRASS) text_fill = "#5F4211" sub_fill = "#7A6645" else: draw.ellipse((x1 + 18, y1 + 26, x1 + 62, y1 + 70), fill=PAPER) draw.line((x1 + 30, y1 + 50, x1 + 38, y1 + 58, x1 + 53, y1 + 39), fill=JADE, width=5) text_fill = PAPER sub_fill = "#DDF2EF" draw.ellipse((x2 - 39, y1 + 18, x2 - 23, y1 + 34), fill=CINNABAR) draw.text((x1 + 76, y1 + 21), title, font=font(SANS_HEAVY, 21), fill=text_fill) draw.text((x1 + 76, y1 + 49), subtitle, font=label_font, fill=sub_fill) if index < 2: cx = 892 draw.line((cx, y2 + 8, cx, y2 + 32), fill=BRASS, width=4) draw.line((cx - 8, y2 + 24, cx, y2 + 32, cx + 8, y2 + 24), fill=BRASS, width=4) return image def main(): ASSETS.mkdir(parents=True, exist_ok=True) EXT_ICONS.mkdir(parents=True, exist_ok=True) mark = draw_mark(512) mark.resize((256, 256), Image.Resampling.LANCZOS).save(ASSETS / "brand-mark.png") draw_share_card().save(ASSETS / "share-card.png") icon_sizes = [16, 32, 48, 64, 128, 256] icons = [mark.resize((size, size), Image.Resampling.LANCZOS) for size in icon_sizes] icons[-1].save(PUBLIC / "favicon.ico", sizes=[(size, size) for size in icon_sizes]) for size in [16, 32, 48, 128]: mark.resize((size, size), Image.Resampling.LANCZOS).save(EXT_ICONS / f"icon{size}.png") if __name__ == "__main__": main()