95 lines
2.5 KiB
JavaScript
95 lines
2.5 KiB
JavaScript
import { deflateSync } from 'zlib'
|
||
import { writeFileSync, mkdirSync } from 'fs'
|
||
import { dirname, resolve } from 'path'
|
||
import { fileURLToPath } from 'url'
|
||
|
||
/** 生成一个简洁的托盘图标(粉色唱片盘),输出到 resources/tray.png */
|
||
const __dirname = dirname(fileURLToPath(import.meta.url))
|
||
|
||
const SIZE = 32
|
||
const cx = SIZE / 2 - 0.5
|
||
const cy = SIZE / 2 - 0.5
|
||
const rOuter = 15
|
||
const rHole = 4
|
||
|
||
function px(x, y) {
|
||
const dx = x - cx
|
||
const dy = y - cy
|
||
const d = Math.sqrt(dx * dx + dy * dy)
|
||
// 抗锯齿边缘
|
||
const edge = (r) => Math.max(0, Math.min(1, r - d + 0.5))
|
||
const inDisc = edge(rOuter)
|
||
const inHole = edge(rHole)
|
||
if (inDisc <= 0) return [0, 0, 0, 0]
|
||
// 唱片主体粉色,中心孔洞白色
|
||
const pink = [255, 55, 95]
|
||
const white = [255, 255, 255]
|
||
const a = Math.round(255 * inDisc)
|
||
if (inHole > 0) {
|
||
const t = inHole
|
||
return [
|
||
Math.round(pink[0] * (1 - t) + white[0] * t),
|
||
Math.round(pink[1] * (1 - t) + white[1] * t),
|
||
Math.round(pink[2] * (1 - t) + white[2] * t),
|
||
a
|
||
]
|
||
}
|
||
return [pink[0], pink[1], pink[2], a]
|
||
}
|
||
|
||
// 构造原始 RGBA 扫描行(每行前置 filter 0)
|
||
const raw = Buffer.alloc((SIZE * 4 + 1) * SIZE)
|
||
let p = 0
|
||
for (let y = 0; y < SIZE; y++) {
|
||
raw[p++] = 0
|
||
for (let x = 0; x < SIZE; x++) {
|
||
const [r, g, b, a] = px(x, y)
|
||
raw[p++] = r
|
||
raw[p++] = g
|
||
raw[p++] = b
|
||
raw[p++] = a
|
||
}
|
||
}
|
||
|
||
const crcTable = (() => {
|
||
const t = []
|
||
for (let n = 0; n < 256; n++) {
|
||
let c = n
|
||
for (let k = 0; k < 8; k++) c = c & 1 ? 0xedb88320 ^ (c >>> 1) : c >>> 1
|
||
t[n] = c >>> 0
|
||
}
|
||
return t
|
||
})()
|
||
function crc32(buf) {
|
||
let c = 0xffffffff
|
||
for (let i = 0; i < buf.length; i++) c = crcTable[(c ^ buf[i]) & 0xff] ^ (c >>> 8)
|
||
return (c ^ 0xffffffff) >>> 0
|
||
}
|
||
function chunk(type, data) {
|
||
const len = Buffer.alloc(4)
|
||
len.writeUInt32BE(data.length, 0)
|
||
const typeBuf = Buffer.from(type, 'ascii')
|
||
const body = Buffer.concat([typeBuf, data])
|
||
const crc = Buffer.alloc(4)
|
||
crc.writeUInt32BE(crc32(body), 0)
|
||
return Buffer.concat([len, body, crc])
|
||
}
|
||
|
||
const sig = Buffer.from([137, 80, 78, 71, 13, 10, 26, 10])
|
||
const ihdr = Buffer.alloc(13)
|
||
ihdr.writeUInt32BE(SIZE, 0)
|
||
ihdr.writeUInt32BE(SIZE, 4)
|
||
ihdr[8] = 8 // bit depth
|
||
ihdr[9] = 6 // color type RGBA
|
||
const png = Buffer.concat([
|
||
sig,
|
||
chunk('IHDR', ihdr),
|
||
chunk('IDAT', deflateSync(raw)),
|
||
chunk('IEND', Buffer.alloc(0))
|
||
])
|
||
|
||
const out = resolve(__dirname, '../resources/tray.png')
|
||
mkdirSync(dirname(out), { recursive: true })
|
||
writeFileSync(out, png)
|
||
console.log('wrote', out, png.length, 'bytes')
|