WorkBuddy 氛围皮肤 — 从图片到桌面皮肤
将任意图片(画作、照片、设计稿)自动转化为 WorkBuddy 桌面皮肤。通过 K-Means 聚类提取图片主色,按色相分桶分配颜色角色,生成纯模糊背景,最终注入 app.asar 生效。
依赖
- pink-crystal-workbuddy-skin:提供 CSS 模板(
assets/dynamic/skin.css为 DARK 模板,assets/dynamic-light/skin.css为 LIGHT 模板) - Python 3 + Pillow + NumPy:颜色提取
- FFmpeg:背景图编码(Pillow 也可直接生成 JPEG)
- Node.js + @electron/asar:asar 解包/重打包
⚠️ 关键注意事项:Electron Asar Integrity
Electron 37+ 对 asar 内的每个文件校验 SHA256 integrity hash。 注入或修改 asar 内的 CSS 文件时,必须确保 integrity 字段正确,否则 CSS 会被静默拒绝加载(界面无任何变化,无报错日志)。
本 skill 的 apply_skin.sh 使用 @electron/asar 的 extract + pack 方式,重打包时自动生成正确的 integrity hash,无需手动处理。
> 不要用删除 integrity 字段的方式做二进制手术(asar binary surgery)。如需使用二进制手术方式(更快,不需要全量重打包),必须计算并写入正确的 SHA256 hash(含分块 hash),参见 references/asar_surgery.mjs。
工作流程
步骤 1:提取配色
运行 scripts/extract_colors.py,输入图片路径:
python3 scripts/extract_colors.py <image_path> --output-dir /tmp/skin_making
脚本自动完成:
1. 自动裁剪白边:从四角采样画纸色,逐行扫描裁掉边框
2. 纯模糊背景:裁剪图拉伸到 1920×1080,高斯模糊 radius=60,顶部渐变压暗
3. K-Means 聚类:K=6,50000 采样点,K-Means++ 初始化,迭代至收敛
4. BT.601 亮度判定:L = 0.299R + 0.587G + 0.114B,加权平均 < 128 → DARK
5. 色相分桶 + 角色分配:
- accent:占比最高的非白/非黑色(最跳眼)
- secondary:不同桶的次高占比色(形成对比)
- surface:同桶中 RGB 总和最小的(最暗,安静退后)
- text:固定值 #F0F5FA(DARK) / #1A1A2E(LIGHT),走 WCAG 对比度
输出文件:
- 01_cropped.jpg:裁剪后画心
- 02_background_blur.jpg:纯模糊背景(无画心,确保文字可读)
- color_scheme.json:完整配色方案
步骤 2:生成皮肤 CSS
运行 scripts/build_skin_css.py:
python3 scripts/build_skin_css.py \
--scheme /tmp/skin_making/color_scheme.json \
--output /tmp/skin_making/skin.css
脚本自动:
1. 根据 mode(DARK/LIGHT)选择正确的 CSS 模板
- DARK → pink-crystal-workbuddy-skin/assets/dynamic/skin.css
- LIGHT → pink-crystal-workbuddy-skin/assets/dynamic-light/skin.css
2. 替换 accent/secondary/text 颜色变量(HEX + rgba 前缀)
3. LIGHT 模式额外替换面板底色为 accent 浅色版本(92% white mix)
4. 将模糊背景编码为 base64 嵌入 CSS
5. 设置背景遮罩透明度(DARK: 0.40, LIGHT: 0.35)
6. 输出完整的皮肤 CSS 文件
验证:脚本运行后会自动检查是否有未替换的占位符或 rgba 语法错误。
步骤 3:安装皮肤
运行 scripts/apply_skin.sh,指定生成的 CSS 文件:
bash scripts/apply_skin.sh --css /tmp/skin_making/skin.css
脚本自动完成:
1. 解包 app.asar 到临时目录(extract)
2. 找到主 CSS 文件 renderer/assets/index-*.css(文件名含 hash,可能随版本变化)
3. 幂等剥离旧皮肤(通过 WORKBUDDY_SKIN 标记识别)
4. 注入新皮肤 CSS
5. 重打包为新 asar(pack,自动生成正确的 integrity hash)
6. 退出 WorkBuddy → 备份原 asar → 替换 → 重签 → 去隔离 → 重启
> 注意:此脚本会退出 WorkBuddy 并重启。不要在 WorkBuddy 内置终端中运行,请在系统终端 Terminal.app 中运行。
步骤 4:回滚
恢复到之前的备份:
# 自动选用最新备份
bash scripts/rollback.sh
# 指定备份
bash scripts/rollback.sh ~/WorkBuddy/App_app.asar.bak.20260804_210000
故障排查
皮肤不生效(界面无变化)
最常见原因:asar 内 CSS 文件的 integrity hash 不正确或缺失。Electron 37+ 会静默拒绝加载没有有效 integrity 的文件。
排查步骤:
1. 确认 apply_skin.sh 使用的是 extract + pack 方式(不是二进制手术)
2. 检查新 asar 中是否包含 WORKBUDDY_SKIN 标识:grep -a -c "WORKBUDDY_SKIN" /Applications/WorkBuddy.app/Contents/Resources/app.asar
3. 确认 WorkBuddy 已重启(不是最小化恢复)
4. 清除 Electron 缓存:rm -rf ~/Library/Application\ Support/WorkBuddy/Cache ~/Library/Application\ Support/WorkBuddy/GPUCache,然后重启
CSS 生成后颜色不对
- 检查
color_scheme.json中的mode字段是否正确(DARK/LIGHT) - 确认
build_skin_css.py选择了正确的模板(DARK 用dynamic/,LIGHT 用dynamic-light/) - 检查生成的 CSS 中是否还有
rgba(255,77,156,(原始模板色未替换)
找不到 CSS 模板
确认 pink-crystal-workbuddy-skin skill 已安装:
ls ~/.workbuddy/skills/pink-crystal-workbuddy-skin/assets/dynamic/skin.css
ls ~/.workbuddy/skills/pink-crystal-workbuddy-skin/assets/dynamic-light/skin.css
重签失败
macOS 代码签名问题,尝试:
codesign --force --deep --sign - /Applications/WorkBuddy.app
xattr -cr /Applications/WorkBuddy.app
关键设计决策
为什么用纯模糊背景?
画作的细节会干扰 UI 文字阅读。纯模糊版只保留色调氛围,消除所有细节,文字浮在上面清晰可读。这就是「氛围皮肤」名称的由来——只取画的色调氛围,不取画的细节。
为什么 text 不走调色板?
文字需要最高对比度。调色板中的颜色可能不适合做文字色(如浅色在浅色背景上不可读)。固定值 #F0F5FA(DARK) / #1A1A2E(LIGHT) 确保 WCAG AAA 合规。
为什么 accent 和 surface 同桶不同选?
accent 取占比最高(亮、跳、抢眼),surface 取 RGB 总和最小(暗、退、安静)。同桶保证色调统一,不同选保证明度反差大——既有整体感又有层次。
为什么用 extract + pack 而不是二进制手术?
二进制手术(直接修改 asar header 的 offset/size)更快,但需要手动计算 SHA256 integrity hash(含分块 hash),容易出错。extract + pack 方式由 @electron/asar 库自动处理 integrity,更可靠。二进制手术方式作为备选方案保留在 references/asar_surgery.mjs 中。
参考文档
references/color_theory.md:配色提取与角色分配的完整原理说明references/asar_surgery.mjs:二进制手术方式的参考实现(含 integrity 计算)