"""深夜茶馆 cover.png 生成:1080x1920,深巷夜色底 + 灯笼暖光 + 茶烟 + 游戏名 + 玩法提示。""" import math import random from PIL import Image, ImageDraw, ImageFilter, ImageChops W, H = 1080, 1920 FONT = "/usr/share/fonts/opentype/noto/NotoSansCJK-Bold.ttc" OUT = "/home/xsl/AutoVideo/SoundGame/web/teahouse/cover.png" random.seed(20) img = Image.new("RGB", (W, H)) px = img.load() # 深巷夜色渐变:上 #0a0d16 -> 下 #1c130a(底部灯笼映暖) top, bottom = (10, 13, 22), (28, 19, 10) for y in range(H): t = y / (H - 1) for x in range(W): px[x, y] = tuple(int(a + (b - a) * t) for a, b in zip(top, bottom)) d = ImageDraw.Draw(img) # 星点(稀疏,都市夜空) for _ in range(140): x, y = random.randint(0, W - 1), random.randint(0, int(H * 0.4)) c = random.randint(90, 180) d.point((x, y), fill=(c, c, min(255, c + 10))) # 两侧屋檐剪影(巷子纵深) d.polygon([(0, 0), (W * 0.30, 0), (W * 0.16, H * 0.34), (0, H * 0.42)], fill=(6, 7, 12)) d.polygon([(W, 0), (W * 0.70, 0), (W * 0.84, H * 0.34), (W, H * 0.42)], fill=(6, 7, 12)) # 巷子尽头茶馆门脸亮窗(暖光小方块) d.rectangle([W * 0.42, H * 0.40, W * 0.58, H * 0.47], fill=(64, 42, 18)) d.rectangle([W * 0.44, H * 0.415, W * 0.56, H * 0.455], fill=(120, 78, 30)) # 主灯笼(中上,暖橙光晕两层) cx, cy = W / 2, H * 0.30 glow = Image.new("RGB", (W, H), (0, 0, 0)) gg = ImageDraw.Draw(glow) gg.ellipse([cx - W * 0.26, cy - W * 0.26, cx + W * 0.26, cy + W * 0.26], fill=(52, 30, 10)) glow = glow.filter(ImageFilter.GaussianBlur(60)) img = ImageChops.add(img, glow) d = ImageDraw.Draw(img) # 灯笼体:椭圆 + 骨架线 d.ellipse([cx - W * 0.115, cy - H * 0.052, cx + W * 0.115, cy + H * 0.052], fill=(196, 84, 32)) for k in range(-3, 4): xk = cx + k * W * 0.028 d.arc([xk - W * 0.10, cy - H * 0.052, xk + W * 0.10, cy + H * 0.052], start=300 if k < 0 else 240, end=420 if k > 0 else 60, fill=(140, 56, 20), width=3) d.rectangle([cx - W * 0.03, cy - H * 0.062, cx + W * 0.03, cy - H * 0.050], fill=(120, 96, 40)) d.rectangle([cx - W * 0.03, cy + H * 0.050, cx + W * 0.03, cy + H * 0.062], fill=(120, 96, 40)) # 灯穗 d.line([cx, cy + H * 0.062, cx, cy + H * 0.095], fill=(160, 40, 30), width=6) # 茶烟(灯笼上方,淡灰曲线) smoke = Image.new("RGBA", (W, H), (0, 0, 0, 0)) sd = ImageDraw.Draw(smoke) for i in range(3): pts = [] for t in range(60): u = t / 59 x = cx + math.sin(u * 6 + i * 2) * 34 * (1 - u * 0.4) + (i - 1) * 26 y = cy - H * 0.075 - u * H * 0.16 pts.append((x, y)) sd.line(pts, fill=(180, 180, 190, 46), width=10) smoke = smoke.filter(ImageFilter.GaussianBlur(6)) img.paste(Image.alpha_composite(img.convert("RGBA"), smoke).convert("RGB"), (0, 0)) # 底部茶盏剪影 + 涟漪声纹环(声音主题) d = ImageDraw.Draw(img) bcx, bcy = W / 2, H * 0.74 d.ellipse([bcx - 130, bcy - 40, bcx + 130, bcy + 60], fill=(14, 10, 8)) d.ellipse([bcx - 108, bcy - 28, bcx + 108, bcy + 34], fill=(46, 30, 14)) for r, a in [(230, 60), (300, 40), (370, 24)]: d.arc([bcx - r, bcy - r * 0.36, bcx + r, bcy + r * 0.36], start=200, end=340, fill=(90, 66, 34), width=4) # 文字 from PIL import ImageFont def font(sz): return ImageFont.truetype(FONT, sz) d.text((W / 2, H * 0.56), "深夜茶馆", font=font(120), fill=(240, 226, 200), anchor="mm") d.text((W / 2, H * 0.615), "茶是热的,夜是长的", font=font(40), fill=(200, 178, 140), anchor="mm") d.text((W / 2, H * 0.885), "左右滑选茶 · 双击提壶出汤 · 上滑状态 · 下滑菜单", font=font(34), fill=(170, 158, 138), anchor="mm") d.text((W / 2, H * 0.925), "纯音频手势游戏 · 戴上耳机更佳", font=font(30), fill=(130, 122, 108), anchor="mm") img.save(OUT) print("saved", OUT)