digital-human-monitor-v2/scripts/generate-icons.mjs
2026-04-18 12:12:16 +08:00

106 lines
2.9 KiB
JavaScript

import { mkdir } from "node:fs/promises";
import path from "node:path";
import { fileURLToPath } from "node:url";
import sharp from "sharp";
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const projectRoot = path.resolve(__dirname, "..");
const sourceIconPath = path.join(projectRoot, "icon.png");
const pwaOutputDir = path.join(projectRoot, "public", "pwa");
const androidResArg = process.argv.find((arg) =>
arg.startsWith("--android-res-dir="),
);
const androidResDir = androidResArg
? androidResArg.slice("--android-res-dir=".length)
: process.env.ANDROID_RES_DIR;
function createCircleMask(size) {
return Buffer.from(`
<svg width="${size}" height="${size}" viewBox="0 0 ${size} ${size}" xmlns="http://www.w3.org/2000/svg">
<circle cx="${size / 2}" cy="${size / 2}" r="${size / 2}" fill="white" />
</svg>
`);
}
async function writePng(size, outputPath) {
await sharp(sourceIconPath).resize(size, size).png().toFile(outputPath);
}
async function writeMaskablePng(size, outputPath) {
const foreground = await sharp(sourceIconPath)
.resize(Math.round(size * 0.82), Math.round(size * 0.82), {
fit: "contain",
})
.png()
.toBuffer();
await sharp({
create: {
width: size,
height: size,
channels: 4,
background: "#FBFAF6",
},
})
.composite([
{
input: foreground,
gravity: "center",
},
])
.png()
.toFile(outputPath);
}
async function writeAndroidIcons(resDir) {
const densities = [
{ dir: "mipmap-mdpi", size: 48 },
{ dir: "mipmap-hdpi", size: 72 },
{ dir: "mipmap-xhdpi", size: 96 },
{ dir: "mipmap-xxhdpi", size: 144 },
{ dir: "mipmap-xxxhdpi", size: 192 },
];
for (const density of densities) {
const targetDir = path.join(resDir, density.dir);
await mkdir(targetDir, { recursive: true });
const squareBuffer = await sharp(sourceIconPath)
.resize(density.size, density.size)
.png()
.toBuffer();
await sharp(squareBuffer)
.webp({ quality: 92 })
.toFile(path.join(targetDir, "ic_launcher.webp"));
await sharp(squareBuffer)
.composite([
{
input: createCircleMask(density.size),
blend: "dest-in",
},
])
.webp({ quality: 92 })
.toFile(path.join(targetDir, "ic_launcher_round.webp"));
}
}
await mkdir(pwaOutputDir, { recursive: true });
await Promise.all([
writePng(192, path.join(pwaOutputDir, "icon-192x192.png")),
writePng(512, path.join(pwaOutputDir, "icon-512x512.png")),
writePng(180, path.join(pwaOutputDir, "apple-touch-icon.png")),
writePng(96, path.join(pwaOutputDir, "badge-96x96.png")),
writeMaskablePng(512, path.join(pwaOutputDir, "icon-maskable-512x512.png")),
]);
if (androidResDir) {
await writeAndroidIcons(androidResDir);
}
console.log("Icons generated successfully.");