Files
2026-06-22 23:03:02 +02:00

80 lines
2.4 KiB
JavaScript

import sharp from "sharp";
import { writeFileSync, readFileSync } from "node:fs";
import { resolve } from "node:path";
const ROOT = resolve(import.meta.dirname, "..");
const SOURCE = resolve(ROOT, "public/image/Studio/Logo Flat.png");
const OUT_ICON_PNG = resolve(ROOT, "app/icon.png");
const OUT_FAVICON_ICO = resolve(ROOT, "app/favicon.ico");
const OUT_PUBLIC_PNG = resolve(ROOT, "public/image/Studio/favicon.png");
const SIZE = 512;
const MOUNTAIN_FILL = 0.78;
async function makeFavicon(size) {
const circle = Buffer.from(
`<svg xmlns="http://www.w3.org/2000/svg" width="${size}" height="${size}">
<circle cx="${size / 2}" cy="${size / 2}" r="${size / 2}" fill="#000000"/>
</svg>`
);
const mountainTargetW = Math.round(size * MOUNTAIN_FILL);
const mountain = await sharp(SOURCE)
.resize({ width: mountainTargetW })
.toBuffer();
const mountainMeta = await sharp(mountain).metadata();
return sharp(circle)
.composite([
{
input: mountain,
top: Math.round((size - mountainMeta.height) / 2) + Math.round(size * 0.03),
left: Math.round((size - mountainMeta.width) / 2),
},
])
.png()
.toBuffer();
}
function buildIco(pngBuffers) {
const count = pngBuffers.length;
const header = Buffer.alloc(6);
header.writeUInt16LE(0, 0);
header.writeUInt16LE(1, 2);
header.writeUInt16LE(count, 4);
const dirEntries = [];
let offset = 6 + count * 16;
for (const { buffer, size } of pngBuffers) {
const entry = Buffer.alloc(16);
entry.writeUInt8(size === 256 ? 0 : size, 0);
entry.writeUInt8(size === 256 ? 0 : size, 1);
entry.writeUInt8(0, 2);
entry.writeUInt8(0, 3);
entry.writeUInt16LE(1, 4);
entry.writeUInt16LE(32, 6);
entry.writeUInt32LE(buffer.length, 8);
entry.writeUInt32LE(offset, 12);
dirEntries.push(entry);
offset += buffer.length;
}
return Buffer.concat([header, ...dirEntries, ...pngBuffers.map((p) => p.buffer)]);
}
const big = await makeFavicon(SIZE);
writeFileSync(OUT_ICON_PNG, big);
writeFileSync(OUT_PUBLIC_PNG, big);
const icoSizes = [16, 32, 48];
const icoPngs = await Promise.all(
icoSizes.map(async (s) => ({
size: s,
buffer: await sharp(big).resize(s, s).png({ compressionLevel: 9 }).toBuffer(),
}))
);
writeFileSync(OUT_FAVICON_ICO, buildIco(icoPngs));
console.log("icon.png:", big.length, "bytes");
console.log("favicon.ico:", readFileSync(OUT_FAVICON_ICO).length, "bytes");