// 从 public/werewolf/back.jpg 生成 PWA 图标。 // 运行: npm run gen:icons import sharp from 'sharp' import { mkdir } from 'node:fs/promises' import { fileURLToPath } from 'node:url' import { dirname, resolve } from 'node:path' const here = dirname(fileURLToPath(import.meta.url)) const pub = resolve(here, '../public') const src = resolve(pub, 'werewolf/back.jpg') // 卡背是竖图,狼头菱形大致在水平居中、垂直 ~46% 处。 // 裁一个聚焦狼头 logo 的方形区域作为图标基底(按实际尺寸夹紧,避免越界)。 const meta = await sharp(src).metadata() const SIDE = Math.min(meta.width, Math.round(meta.width * 0.82), meta.height) const left = Math.round(Math.max(0, Math.min(meta.width - SIDE, meta.width / 2 - SIDE / 2))) const top = Math.round(Math.max(0, Math.min(meta.height - SIDE, meta.height * 0.46 - SIDE / 2))) const square = await sharp(src) .extract({ left, top, width: SIDE, height: SIDE }) .toBuffer() const RED = { r: 0xc0, g: 0x39, b: 0x2b, alpha: 1 } // 贴近卡面红,用于 maskable 安全区留白 async function out(name, size, { maskable = false } = {}) { await mkdir(pub, { recursive: true }) const target = resolve(pub, name) if (maskable) { // maskable: 内容缩到 ~80%,四周用卡红留白,保证安全区不被裁切 const inner = Math.round(size * 0.8) const fg = await sharp(square).resize(inner, inner).toBuffer() await sharp({ create: { width: size, height: size, channels: 4, background: RED } }) .composite([{ input: fg, gravity: 'center' }]) .png() .toFile(target) } else { await sharp(square).resize(size, size).png().toFile(target) } console.log('wrote', name) } await out('pwa-192x192.png', 192) await out('pwa-512x512.png', 512) await out('maskable-icon-512x512.png', 512, { maskable: true }) await out('apple-touch-icon-180x180.png', 180) await out('favicon-48x48.png', 48) console.log('done')