// Minimal PNG writer (8-bit RGBA, no interlace). Node only: the browser demo // blits the same framebuffer through ImageData and never touches this file. import zlib from 'node:zlib'; const CRC_TABLE = (() => { const t = new Uint32Array(256); 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 = CRC_TABLE[(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 body = Buffer.concat([Buffer.from(type, 'ascii'), data]); const crc = Buffer.alloc(4); crc.writeUInt32BE(crc32(body), 0); return Buffer.concat([len, body, crc]); } /** * @param {number} width * @param {number} height * @param {Uint8ClampedArray|Uint8Array} rgba * @returns {Buffer} */ export function encodePNG(width, height, rgba) { const stride = width * 4; const raw = Buffer.alloc((stride + 1) * height); for (let y = 0; y < height; y++) { raw[y * (stride + 1)] = 0; raw.set(rgba.subarray(y * stride, (y + 1) * stride), y * (stride + 1) + 1); } const ihdr = Buffer.alloc(13); ihdr.writeUInt32BE(width, 0); ihdr.writeUInt32BE(height, 4); ihdr[8] = 8; ihdr[9] = 6; ihdr[10] = 0; ihdr[11] = 0; ihdr[12] = 0; return Buffer.concat([ Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]), chunk('IHDR', ihdr), chunk('IDAT', zlib.deflateSync(raw, { level: 6 })), chunk('IEND', Buffer.alloc(0)), ]); } /** Tile several bitmaps into one grid. Bitmaps are {w, h, data}. */ export function tile(bitmaps, cols, rows, gap = 0) { if (!bitmaps.length) throw new Error('tile: nothing to tile'); const w = bitmaps[0].w; const h = bitmaps[0].h; const W = cols * w + (cols - 1) * gap; const H = rows * h + (rows - 1) * gap; const out = new Uint8ClampedArray(W * H * 4); for (let i = 0; i < out.length; i += 4) { out[i] = 8; out[i + 1] = 8; out[i + 2] = 10; out[i + 3] = 255; } for (let i = 0; i < bitmaps.length; i++) { const b = bitmaps[i]; const cx = i % cols; const cy = (i / cols) | 0; const ox = cx * (w + gap); const oy = cy * (h + gap); for (let y = 0; y < h; y++) { const src = y * b.w * 4; const dst = ((oy + y) * W + ox) * 4; out.set(b.data.subarray(src, src + b.w * 4), dst); } } return { w: W, h: H, data: out }; }