mirror of
https://gh-proxy.org/https://github.com/klinsmaya/LobsterAI.git
synced 2026-11-08 01:06:44 +08:00
114 lines
3.7 KiB
JavaScript
114 lines
3.7 KiB
JavaScript
/**
|
||
* Generate a multi-size .ico file from a source PNG.
|
||
*
|
||
* ICO format with embedded PNGs:
|
||
* ICONDIR (6 bytes) – reserved(2) + type(2)=1 + count(2)
|
||
* ICONDIRENTRY * N (16 bytes each)
|
||
* PNG data blobs
|
||
*
|
||
* We use System.Drawing via PowerShell to resize the source image into
|
||
* several sizes, save them as temporary PNGs, then pack them into .ico
|
||
* purely with Node buffers.
|
||
*/
|
||
|
||
const fs = require('fs');
|
||
const path = require('path');
|
||
const { execSync } = require('child_process');
|
||
|
||
const SOURCE = path.join(__dirname, '..', 'public', 'logo.png');
|
||
const OUT_DIR = path.join(__dirname, '..', 'build', 'icons', 'win');
|
||
const OUT_ICO = path.join(OUT_DIR, 'icon.ico');
|
||
const SIZES = [256, 128, 64, 48, 32, 16];
|
||
|
||
// Ensure output directory exists
|
||
fs.mkdirSync(OUT_DIR, { recursive: true });
|
||
|
||
// Step 1: Use PowerShell + System.Drawing to resize the source PNG
|
||
const tmpDir = path.join(__dirname, '..', 'build', 'icons', '_tmp');
|
||
fs.mkdirSync(tmpDir, { recursive: true });
|
||
|
||
const psScript = `
|
||
Add-Type -AssemblyName System.Drawing
|
||
|
||
$src = [System.Drawing.Image]::FromFile("${SOURCE.replace(/\\/g, '\\\\')}")
|
||
$sizes = @(${SIZES.join(',')})
|
||
|
||
foreach ($s in $sizes) {
|
||
$bmp = New-Object System.Drawing.Bitmap($s, $s)
|
||
$g = [System.Drawing.Graphics]::FromImage($bmp)
|
||
$g.InterpolationMode = [System.Drawing.Drawing2D.InterpolationMode]::HighQualityBicubic
|
||
$g.SmoothingMode = [System.Drawing.Drawing2D.SmoothingMode]::HighQuality
|
||
$g.PixelOffsetMode = [System.Drawing.Drawing2D.PixelOffsetMode]::HighQuality
|
||
$g.CompositingQuality = [System.Drawing.Drawing2D.CompositingQuality]::HighQuality
|
||
$g.DrawImage($src, 0, 0, $s, $s)
|
||
$g.Dispose()
|
||
$outPath = "${tmpDir.replace(/\\/g, '\\\\')}\\\\icon_$s.png"
|
||
$bmp.Save($outPath, [System.Drawing.Imaging.ImageFormat]::Png)
|
||
$bmp.Dispose()
|
||
}
|
||
|
||
$src.Dispose()
|
||
`;
|
||
|
||
const psFile = path.join(tmpDir, 'resize.ps1');
|
||
fs.writeFileSync(psFile, psScript, 'utf8');
|
||
execSync(`powershell -ExecutionPolicy Bypass -File "${psFile}"`, { stdio: 'inherit' });
|
||
|
||
// Step 2: Read resized PNGs and pack into ICO
|
||
const pngBuffers = SIZES.map(s => {
|
||
const p = path.join(tmpDir, `icon_${s}.png`);
|
||
return { size: s, data: fs.readFileSync(p) };
|
||
});
|
||
|
||
const count = pngBuffers.length;
|
||
const headerSize = 6;
|
||
const entrySize = 16;
|
||
const dataOffset0 = headerSize + entrySize * count;
|
||
|
||
// Calculate offsets
|
||
let currentOffset = dataOffset0;
|
||
const entries = pngBuffers.map(({ size, data }) => {
|
||
const entry = {
|
||
width: size >= 256 ? 0 : size, // 0 means 256 in ICO format
|
||
height: size >= 256 ? 0 : size,
|
||
dataSize: data.length,
|
||
offset: currentOffset,
|
||
data,
|
||
};
|
||
currentOffset += data.length;
|
||
return entry;
|
||
});
|
||
|
||
// Build ICO buffer
|
||
const totalSize = currentOffset;
|
||
const ico = Buffer.alloc(totalSize);
|
||
|
||
// ICONDIR
|
||
ico.writeUInt16LE(0, 0); // reserved
|
||
ico.writeUInt16LE(1, 2); // type = ICO
|
||
ico.writeUInt16LE(count, 4); // image count
|
||
|
||
// ICONDIRENTRY for each image
|
||
entries.forEach((e, i) => {
|
||
const off = headerSize + i * entrySize;
|
||
ico.writeUInt8(e.width, off + 0); // width
|
||
ico.writeUInt8(e.height, off + 1); // height
|
||
ico.writeUInt8(0, off + 2); // color palette
|
||
ico.writeUInt8(0, off + 3); // reserved
|
||
ico.writeUInt16LE(1, off + 4); // color planes
|
||
ico.writeUInt16LE(32, off + 6); // bits per pixel
|
||
ico.writeUInt32LE(e.dataSize, off + 8); // image data size
|
||
ico.writeUInt32LE(e.offset, off + 12); // image data offset
|
||
});
|
||
|
||
// Image data
|
||
entries.forEach(e => {
|
||
e.data.copy(ico, e.offset);
|
||
});
|
||
|
||
fs.writeFileSync(OUT_ICO, ico);
|
||
console.log(`Generated ${OUT_ICO} (${SIZES.join(', ')}px) — ${ico.length} bytes`);
|
||
|
||
// Cleanup temp files
|
||
fs.rmSync(tmpDir, { recursive: true, force: true });
|