80 lines
2.4 KiB
JavaScript
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");
|