|
| 1 | +import fs from 'node:fs/promises'; |
| 2 | +import path from 'node:path'; |
| 3 | +import sharp from 'sharp'; |
| 4 | + |
| 5 | +const root = process.cwd(); |
| 6 | +const source = path.join(root, 'src-tauri', 'icons', 'icon.png'); |
| 7 | +const outDir = path.join(root, 'src-tauri', 'installer-assets'); |
| 8 | +const sidebarImage = path.join(outDir, 'nsis-sidebar.bmp'); |
| 9 | +const headerImage = path.join(outDir, 'nsis-header.bmp'); |
| 10 | +const installerIcon = path.join(root, 'src-tauri', 'icons', 'icon.ico'); |
| 11 | + |
| 12 | +await fs.mkdir(outDir, { recursive: true }); |
| 13 | + |
| 14 | +const toBmp24 = async (inputPath, width, height, outputPath) => { |
| 15 | + const { data, info } = await sharp(inputPath) |
| 16 | + .resize(width, height, { fit: 'cover' }) |
| 17 | + .flatten({ background: '#111827' }) |
| 18 | + .ensureAlpha() |
| 19 | + .raw() |
| 20 | + .toBuffer({ resolveWithObject: true }); |
| 21 | + |
| 22 | + const bytesPerPixel = 3; |
| 23 | + const rowStride = info.width * bytesPerPixel; |
| 24 | + const paddedRowStride = Math.ceil(rowStride / 4) * 4; |
| 25 | + const pixelDataSize = paddedRowStride * info.height; |
| 26 | + const fileHeaderSize = 14; |
| 27 | + const dibHeaderSize = 40; |
| 28 | + const pixelDataOffset = fileHeaderSize + dibHeaderSize; |
| 29 | + const fileSize = pixelDataOffset + pixelDataSize; |
| 30 | + const bmp = Buffer.alloc(fileSize); |
| 31 | + |
| 32 | + bmp.write('BM', 0, 2, 'ascii'); |
| 33 | + bmp.writeUInt32LE(fileSize, 2); |
| 34 | + bmp.writeUInt32LE(pixelDataOffset, 10); |
| 35 | + bmp.writeUInt32LE(dibHeaderSize, 14); |
| 36 | + bmp.writeInt32LE(info.width, 18); |
| 37 | + bmp.writeInt32LE(info.height, 22); |
| 38 | + bmp.writeUInt16LE(1, 26); |
| 39 | + bmp.writeUInt16LE(24, 28); |
| 40 | + bmp.writeUInt32LE(0, 30); |
| 41 | + bmp.writeUInt32LE(pixelDataSize, 34); |
| 42 | + |
| 43 | + let outOffset = pixelDataOffset; |
| 44 | + for (let y = info.height - 1; y >= 0; y--) { |
| 45 | + for (let x = 0; x < info.width; x++) { |
| 46 | + const inOffset = (y * info.width + x) * info.channels; |
| 47 | + const r = data[inOffset]; |
| 48 | + const g = data[inOffset + 1]; |
| 49 | + const b = data[inOffset + 2]; |
| 50 | + bmp[outOffset++] = b; |
| 51 | + bmp[outOffset++] = g; |
| 52 | + bmp[outOffset++] = r; |
| 53 | + } |
| 54 | + while ((outOffset - pixelDataOffset) % paddedRowStride !== 0) { |
| 55 | + bmp[outOffset++] = 0x00; |
| 56 | + } |
| 57 | + } |
| 58 | + |
| 59 | + await fs.writeFile(outputPath, bmp); |
| 60 | +}; |
| 61 | + |
| 62 | +await toBmp24(source, 164, 314, sidebarImage); |
| 63 | +await toBmp24(source, 150, 57, headerImage); |
| 64 | + |
| 65 | +console.log('NSIS branding assets prepared:'); |
| 66 | +console.log(`- ${headerImage}`); |
| 67 | +console.log(`- ${sidebarImage}`); |
| 68 | +console.log(`- ${installerIcon}`); |
0 commit comments