91 lines
3.0 KiB
JavaScript
91 lines
3.0 KiB
JavaScript
/* 자체 PNG 생성기 — 외부 의존 없이 WISE 블루(#1f29fc) 배경 + 흰색 'ITMS' 아이콘. */
|
|
const zlib = require('zlib')
|
|
const fs = require('fs')
|
|
const path = require('path')
|
|
|
|
const SIZE = 1024
|
|
const BG = [0x1f, 0x29, 0xfc, 0xff] // WISE blue
|
|
const FG = [0xff, 0xff, 0xff, 0xff] // white
|
|
|
|
// 5x7 비트맵 글리프
|
|
const GLYPHS = {
|
|
I: ['11111', '00100', '00100', '00100', '00100', '00100', '11111'],
|
|
T: ['11111', '00100', '00100', '00100', '00100', '00100', '00100'],
|
|
M: ['10001', '11011', '10101', '10101', '10001', '10001', '10001'],
|
|
S: ['01111', '10000', '10000', '01110', '00001', '00001', '11110'],
|
|
}
|
|
const WORD = 'ITMS'
|
|
|
|
function makeBuffer() {
|
|
const buf = Buffer.alloc(SIZE * SIZE * 4)
|
|
for (let i = 0; i < SIZE * SIZE; i++) buf.set(BG, i * 4)
|
|
|
|
// 글리프 레이아웃: 4글자 * 5px + 간격 1px = 23 units 폭, 7 units 높이
|
|
const cols = WORD.length * 5 + (WORD.length - 1) // 23
|
|
const rows = 7
|
|
const scale = Math.floor((SIZE * 0.62) / cols) // 폭 62% 사용
|
|
const wpx = cols * scale
|
|
const hpx = rows * scale
|
|
const ox = Math.floor((SIZE - wpx) / 2)
|
|
const oy = Math.floor((SIZE - hpx) / 2)
|
|
|
|
const setPx = (x, y) => {
|
|
if (x < 0 || y < 0 || x >= SIZE || y >= SIZE) return
|
|
buf.set(FG, (y * SIZE + x) * 4)
|
|
}
|
|
|
|
let cx = ox
|
|
for (const ch of WORD) {
|
|
const g = GLYPHS[ch]
|
|
for (let r = 0; r < 7; r++) {
|
|
for (let c = 0; c < 5; c++) {
|
|
if (g[r][c] === '1') {
|
|
for (let dy = 0; dy < scale; dy++)
|
|
for (let dx = 0; dx < scale; dx++)
|
|
setPx(cx + c * scale + dx, oy + r * scale + dy)
|
|
}
|
|
}
|
|
}
|
|
cx += 6 * scale // 5 글자폭 + 1 간격
|
|
}
|
|
return buf
|
|
}
|
|
|
|
function crc32(buf) {
|
|
let c = ~0
|
|
for (let i = 0; i < buf.length; i++) {
|
|
c ^= buf[i]
|
|
for (let k = 0; k < 8; k++) c = (c >>> 1) ^ (0xedb88320 & -(c & 1))
|
|
}
|
|
return ~c >>> 0
|
|
}
|
|
function chunk(type, data) {
|
|
const len = Buffer.alloc(4); len.writeUInt32BE(data.length, 0)
|
|
const t = Buffer.from(type, 'ascii')
|
|
const body = Buffer.concat([t, data])
|
|
const crc = Buffer.alloc(4); crc.writeUInt32BE(crc32(body), 0)
|
|
return Buffer.concat([len, body, crc])
|
|
}
|
|
function encodePng(rgba) {
|
|
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; ihdr[9] = 6; ihdr[10] = 0; ihdr[11] = 0; ihdr[12] = 0
|
|
// 각 스캔라인 앞 filter byte 0
|
|
const raw = Buffer.alloc(SIZE * (SIZE * 4 + 1))
|
|
for (let y = 0; y < SIZE; y++) {
|
|
raw[y * (SIZE * 4 + 1)] = 0
|
|
rgba.copy(raw, y * (SIZE * 4 + 1) + 1, y * SIZE * 4, (y + 1) * SIZE * 4)
|
|
}
|
|
const idat = zlib.deflateSync(raw, { level: 9 })
|
|
return Buffer.concat([sig, chunk('IHDR', ihdr), chunk('IDAT', idat), chunk('IEND', Buffer.alloc(0))])
|
|
}
|
|
|
|
const png = encodePng(makeBuffer())
|
|
const outDir = path.join(__dirname, '..', 'assets')
|
|
fs.mkdirSync(outDir, { recursive: true })
|
|
for (const name of ['icon.png', 'adaptive-icon.png', 'splash.png']) {
|
|
fs.writeFileSync(path.join(outDir, name), png)
|
|
}
|
|
console.log('generated', png.length, 'bytes ->', outDir)
|