/* 자체 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)