itsm_ysm/mobile/scripts/gen_icon.js
itms-merge-dev efc3e66635 feat(itms): 모바일 앱 신규 (Expo, 12화면, OAuth password grant)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-19 17:10:46 +09:00

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)