// 生成 PWA 图标:复用 index.html 内联 favicon 的同一套图形语言(靛蓝圆角方块 + 时钟指针)。 // 用 Chromium 渲染 SVG 截图(omitBackground 保住透明圆角),再用 PIL 缩放出各尺寸。 // 一次性工具:图标改版时重跑 png/gen_icons.js 即可。 const puppeteer = require('/root/.openclaw/workspace/web_tasks/node_modules/puppeteer'); const fs = require('fs'); const path = require('path'); const OUT = path.join(__dirname, '..', 'icons'); // any:圆角方块,四角透明(浏览器/桌面场景显示) const svgAny = (s) => ` `; // maskable:满幅无圆角(由系统裁切),图形缩进 80% 安全区 const svgMaskable = (s) => ` `; // apple-touch-icon:满幅无圆角(iOS 自行加圆角) const svgApple = (s) => ` `; (async () => { const browser = await puppeteer.launch({ executablePath: '/usr/bin/chromium', headless: 'new', args: ['--no-sandbox', '--disable-gpu', '--disable-dev-shm-usage'] }); const page = await browser.newPage(); async function shoot(svg, width, height, file) { await page.setViewport({ width, height }); await page.setContent(`${svg}`); await page.screenshot({ path: file, omitBackground: true, clip: { x: 0, y: 0, width, height } }); } await shoot(svgAny(512), 512, 512, path.join(OUT, 'icon-512.png')); await shoot(svgMaskable(512), 512, 512, path.join(OUT, 'icon-maskable-512.png')); await shoot(svgApple(180), 180, 180, path.join(OUT, 'apple-touch-icon.png')); await browser.close(); // 高分辨率原图就位,小尺寸用 PIL Lanczos 缩(比再截一轮更快更稳) const { execSync } = require('child_process'); execSync(`python3 - <<'PY' from PIL import Image import os OUT = ${JSON.stringify(OUT)} Image.open(os.path.join(OUT, 'icon-512.png')).resize((192,192), Image.LANCZOS).save(os.path.join(OUT, 'icon-192.png'), optimize=True) Image.open(os.path.join(OUT, 'icon-maskable-512.png')).resize((192,192), Image.LANCZOS).save(os.path.join(OUT, 'icon-maskable-192.png'), optimize=True) print('icons done') PY`, { stdio: 'inherit' }); for (const f of fs.readdirSync(OUT)) { const st = fs.statSync(path.join(OUT, f)); console.log(f, st.size, 'bytes'); } })().catch(e => { console.error('FAILED:', e.message); process.exit(1); });