# -*- coding: utf-8 -*- """渲染手表端「液态玻璃」UI 预览图。 不是真机截图,是按 492x602 / density 1.4125 的真实参数用 Pillow 画的 设计预览。视觉按 MainActivity 里的实际实现来: 背景 斜向三段渐变(浅蓝 → 浅紫 → 浅粉) 玻璃 白色 70% + 1dp 亮边 + 圆角 18dp 高光 每块玻璃顶部一条更亮的渐变,模拟反光 Tab 玻璃容器 + 一个会滑过去的蓝色胶囊指示器 """ from __future__ import annotations import sys from pathlib import Path from PIL import Image, ImageDraw, ImageFilter, ImageFont # ---- 与 MainActivity 一致的配色 ---- C_FG = (0x1F, 0x23, 0x28) C_ACCENT = (0x18, 0x5C, 0xE8) C_OK = (0x1A, 0x7F, 0x37) C_DANGER = (0xCF, 0x22, 0x2E) C_DIM = (0x65, 0x6D, 0x76) GLASS_BG_TOP = (0xE8, 0xF0, 0xFF) GLASS_BG_MID = (0xF1, 0xEB, 0xFF) GLASS_BG_BOT = (0xFF, 0xF0, 0xF6) GLASS_ALPHA = 0.70 GLASS_EDGE_ALPHA = 0.60 WIDTH_PX = 492 HEIGHT_PX = 602 DENSITY = 1.4125 TABS = [ ("媒体", ["播放/暂停", "停止", "上一曲", "下一曲", "音量 +", "音量 -", "静音"]), ("屏幕", ["屏幕镜像", "截屏", "投图到电脑", "放音到电脑", "关显示器", "设为壁纸"]), ("文件", ["发送文件", "接收文件", "剪贴板→电脑", "取电脑剪贴板", "剪贴板历史", "打开文件夹"]), ("语音", ["语音播报", "音色", "朗读剪贴板", "电脑音乐", "弹通知"]), ("电脑", ["快捷命令", "打开网址", "进程管理", "性能", "磁盘", "电量"]), ("系统", ["锁屏", "睡眠", "关机", "重启", "取消定时关机", "定时关机", "设备诊断"]), ("功能", ["显示", "音量", "系统", "查看", "效率", "教学", "网络", "文件", "工具", "媒体"]), ("娱乐", ["全屏弹幕", "滚动字幕", "抽奖", "掷骰子", "抛硬币", "3-2-1", "影院模式", "随机放歌", "播放视频", "计分板", "改分数", "加油动画"]), ("极客", ["数字雨", "星空穿梭", "全屏监控", "生成二维码", "算哈希", "Base64 编码", "Base64 解码", "生成 UUID", "时间戳", "JSON 格式化", "鼠标取色", "网络测速", "端口检查"]), ] FONTS = [r"C:\Windows\Fonts\msyh.ttc", r"C:\Windows\Fonts\msyhbd.ttc", r"C:\Windows\Fonts\simhei.ttf"] def font(px: int): for path in FONTS: if Path(path).is_file(): try: return ImageFont.truetype(path, px) except Exception: continue return ImageFont.load_default() def dp(value: float) -> int: return int(round(value * DENSITY)) def gradient_background(width: int, height: int) -> Image.Image: """斜向三段渐变(先画小的再放大,省时间也够平滑)。""" small_w, small_h = 96, 96 small = Image.new("RGB", (small_w, small_h)) pixels = small.load() stops = [GLASS_BG_TOP, GLASS_BG_MID, GLASS_BG_BOT] for y in range(small_h): for x in range(small_w): t = (x / (small_w - 1) + y / (small_h - 1)) / 2.0 scaled = t * (len(stops) - 1) index = min(int(scaled), len(stops) - 2) local = scaled - index a, b = stops[index], stops[index + 1] pixels[x, y] = tuple( int(a[i] + (b[i] - a[i]) * local) for i in range(3)) return small.resize((width, height), Image.BICUBIC) def glass_panel(base: Image.Image, box, radius, fill_alpha=GLASS_ALPHA, tint=None, edge_alpha=GLASS_EDGE_ALPHA): """在 base 上叠一块玻璃:白色半透明 + 亮边 + 顶部高光。""" x0, y0, x1, y1 = box width, height = max(1, x1 - x0), max(1, y1 - y0) overlay = Image.new("RGBA", (width, height), (0, 0, 0, 0)) draw = ImageDraw.Draw(overlay) base_color = tint if tint else (255, 255, 255) alpha = int(255 * fill_alpha) draw.rounded_rectangle([0, 0, width - 1, height - 1], radius=radius, fill=(base_color[0], base_color[1], base_color[2], alpha)) # 顶部高光:越靠上越亮 highlight = Image.new("RGBA", (width, height), (0, 0, 0, 0)) hdraw = ImageDraw.Draw(highlight) for i in range(min(height, radius * 2)): strength = int(38 * (1 - i / max(1, radius * 2))) hdraw.line([(0, i), (width, i)], fill=(255, 255, 255, strength)) mask = Image.new("L", (width, height), 0) ImageDraw.Draw(mask).rounded_rectangle([0, 0, width - 1, height - 1], radius=radius, fill=255) overlay = Image.alpha_composite(overlay, Image.composite( highlight, Image.new("RGBA", (width, height), (0, 0, 0, 0)), mask)) # 亮边 draw = ImageDraw.Draw(overlay) draw.rounded_rectangle([0, 0, width - 1, height - 1], radius=radius, outline=(255, 255, 255, int(255 * edge_alpha)), width=1) base.alpha_composite(overlay, (x0, y0)) def render_tab(tab_index: int, name: str, labels: list) -> Image.Image: img = gradient_background(WIDTH_PX, HEIGHT_PX).convert("RGBA") d = ImageDraw.Draw(img) f_title = font(dp(12)) f_tab = font(dp(12)) f_btn = font(dp(11)) f_small = font(dp(9)) pad = dp(8) y = pad # ---- 顶栏 ---- bar_h = dp(38) glass_panel(img, (pad, y, WIDTH_PX - pad, y + bar_h), dp(16)) d = ImageDraw.Draw(img) d.ellipse([pad + dp(9), y + bar_h // 2 - dp(3), pad + dp(15), y + bar_h // 2 + dp(3)], fill=C_OK) d.text((pad + dp(20), y + bar_h // 2), "DESKTOP-RN5I625", font=f_title, fill=C_FG, anchor="lm") d.text((pad + dp(20), y + bar_h // 2 + dp(7)), "192.168.2.64", font=font(dp(7)), fill=C_DIM, anchor="lm") bw = dp(58) glass_panel(img, (WIDTH_PX - pad - dp(6) - bw, y + dp(5), WIDTH_PX - pad - dp(6), y + bar_h - dp(5)), dp(11), tint=C_DANGER, fill_alpha=0.92) d = ImageDraw.Draw(img) d.text((WIDTH_PX - pad - dp(6) - bw // 2, y + bar_h // 2), "断开", font=f_btn, fill=(255, 255, 255), anchor="mm") y += bar_h + dp(8) # ---- Tab 栏(玻璃容器 + 滑动指示器)---- tab_h = dp(30) glass_panel(img, (pad, y, WIDTH_PX - pad, y + tab_h + dp(6)), dp(18)) d = ImageDraw.Draw(img) x = pad + dp(4) indicator = None for i, (tname, _items) in enumerate(TABS): tw = dp(10) * len(tname) + dp(22) if i == tab_index: indicator = (x, y + dp(3), x + tw, y + dp(3) + tab_h) x += tw if indicator: # 指示器:蓝色胶囊 + 顶部高光 ix0, iy0, ix1, iy1 = indicator glass_panel(img, indicator, dp(14), tint=C_ACCENT, fill_alpha=0.94, edge_alpha=0.45) d = ImageDraw.Draw(img) d = ImageDraw.Draw(img) x = pad + dp(4) right_limit = WIDTH_PX - pad - dp(4) for i, (tname, _items) in enumerate(TABS): tw = dp(10) * len(tname) + dp(22) if x + tw > right_limit: break on = (i == tab_index) d.text((x + tw // 2, y + dp(3) + tab_h // 2), tname, font=f_tab, fill=(255, 255, 255) if on else C_FG, anchor="mm") x += tw # 右边如果还有标签,画个渐隐提示可以滑 if x < right_limit: for k in range(dp(14)): alpha = int(200 * (1 - k / dp(14))) d.line([(right_limit - dp(14) + k, y + dp(3)), (right_limit - dp(14) + k, y + dp(3) + tab_h)], fill=(0xEE, 0xEE, 0xF6, alpha)) y += tab_h + dp(18) # ---- 按钮网格 ---- log_h = dp(48) grid_bottom = HEIGHT_PX - pad - log_h - dp(8) columns = 3 cell_h = dp(58) gap = dp(5) content_w = WIDTH_PX - pad * 2 cell_w = (content_w - gap * (columns - 1)) // columns for index, label in enumerate(labels): row = index // columns col = index % columns bx = pad + col * (cell_w + gap) by = y + row * (cell_h + gap) if by + cell_h > grid_bottom: break glass_panel(img, (bx, by, bx + cell_w, by + cell_h), dp(16)) d = ImageDraw.Draw(img) text = label if d.textlength(text, font=f_btn) > cell_w - dp(8): while text and d.textlength(text + "…", font=f_btn) > cell_w - dp(8): text = text[:-1] text += "…" d.text((bx + cell_w // 2, by + cell_h // 2), text, font=f_btn, fill=C_FG, anchor="mm") # ---- 日志 ---- log_top = HEIGHT_PX - pad - log_h glass_panel(img, (pad, log_top, WIDTH_PX - pad, log_top + log_h), dp(14), fill_alpha=0.82) d = ImageDraw.Draw(img) d.text((pad + dp(8), log_top + dp(8)), "[21:20] 已连接 192.168.2.64", font=f_small, fill=C_DIM, anchor="lt") d.text((pad + dp(8), log_top + dp(24)), "[21:20] 电脑端共 105 个功能", font=f_small, fill=C_DIM, anchor="lt") d.text((WIDTH_PX // 2, HEIGHT_PX - dp(1)), f"设计预览(非真机截图)· 液态玻璃 · 分页「{name}」", font=f_small, fill=C_DIM, anchor="mb") return img.convert("RGB") def main() -> int: out_dir = Path(r"C:\Users\shu\Desktop\deepseekagent\LanRemote\_build") out_dir.mkdir(parents=True, exist_ok=True) # 主展示页:媒体 media_index = 0 cover = render_tab(media_index, TABS[media_index][0], TABS[media_index][1]) cover.save(out_dir / "watch_preview.png") print(f" 单页预览: {out_dir / 'watch_preview.png'}") # 九页总览 tiles = [render_tab(i, n, l) for i, (n, l) in enumerate(TABS)] scale = 0.46 tw, th = int(WIDTH_PX * scale), int(HEIGHT_PX * scale) gap = 10 cols = 5 rows = (len(tiles) + cols - 1) // cols total_w = tw * cols + gap * (cols + 1) total_h = th * rows + gap * (rows + 1) sheet = Image.new("RGB", (total_w, total_h), (0xE4, 0xE8, 0xF0)) for i, tile in enumerate(tiles): row, col = divmod(i, cols) sheet.paste(tile.resize((tw, th), Image.LANCZOS), (gap + col * (tw + gap), gap + row * (th + gap))) sheet.save(out_dir / "watch_preview_all.png") print(f" 总览: {out_dir / 'watch_preview_all.png'} {sheet.size}") return 0 if __name__ == "__main__": sys.exit(main())