Files
NASOpenClawRunTime/gen-png.cjs
2026-04-25 01:38:03 +00:00

98 lines
3.3 KiB
JavaScript

// Pure JS PNG Generator for QR codes
// Write a minimal valid PNG with no external dependencies
const fs = require('fs');
const zlib = require('zlib');
function crc32(buf) {
let crc = 0xFFFFFFFF;
const table = new Uint32Array(256);
for (let i = 0; i < 256; i++) {
let c = i;
for (let k = 0; k < 8; k++) c = (c & 1) ? 0xEDB88320 ^ (c >>> 1) : c >>> 1;
table[i] = c;
}
for (let i = 0; i < buf.length; i++) crc = table[(crc ^ buf[i]) & 0xFF] ^ (crc >>> 8);
return (crc ^ 0xFFFFFFFF) >>> 0;
}
function makeChunk(type, data) {
const typeB = Buffer.from(type);
const lenB = Buffer.alloc(4);
lenB.writeUInt32BE(data.length);
const crcB = Buffer.alloc(4);
crcB.writeUInt32BE(crc32(Buffer.concat([typeB, data])));
return Buffer.concat([lenB, typeB, data, crcB]);
}
function createPNG(matrix) {
const size = matrix.length;
const scale = 10;
const outSize = size * scale;
// Raw image data: filter byte (0) + RGB pixels per row
const raw = Buffer.alloc((1 + size * 3) * outSize);
for (let y = 0; y < outSize; y++) {
const rowOffset = y * (1 + outSize * 3);
raw[rowOffset] = 0; // filter type: none
const moduleY = Math.floor(y / scale);
for (let x = 0; x < outSize; x++) {
const moduleX = Math.floor(x / scale);
const pixel = matrix[moduleY][moduleX] ? 0 : 255;
const offset = rowOffset + 1 + x * 3;
raw[offset] = pixel; // R
raw[offset + 1] = pixel; // G
raw[offset + 2] = pixel; // B
}
}
const compressed = zlib.deflateSync(raw);
// PNG signature
const signature = Buffer.from([0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A]);
// IHDR chunk
const ihdr = Buffer.alloc(13);
ihdr.writeUInt32BE(outSize, 0); // width
ihdr.writeUInt32BE(outSize, 4); // height
ihdr[8] = 8; // bit depth
ihdr[9] = 2; // color type (RGB)
ihdr[10] = 0; // compression
ihdr[11] = 0; // filter
ihdr[12] = 0; // interlace
const ihdrChunk = makeChunk('IHDR', ihdr);
const idatChunk = makeChunk('IDAT', compressed);
const iendChunk = makeChunk('IEND', Buffer.alloc(0));
return Buffer.concat([signature, ihdrChunk, idatChunk, iendChunk]);
}
// Matrix from our QR generator (21x21 for "TEST")
const matrix = [
[1,1,1,1,1,1,1,0,1,1,1,0,1,1,1,0,1,1,1,1,1],
[1,0,0,0,0,0,1,0,1,0,1,0,1,0,1,0,0,0,0,0,1],
[1,0,1,1,1,0,1,0,1,1,0,1,0,1,0,1,1,1,0,0,1],
[1,0,1,1,1,0,1,0,0,1,1,0,1,0,1,0,1,1,1,0,1],
[1,0,1,1,1,0,1,0,1,0,1,0,0,1,0,1,1,1,0,0,1],
[1,0,0,0,0,0,1,0,1,1,0,1,1,1,1,1,0,0,0,0,1],
[1,1,1,1,1,1,1,0,0,0,0,0,1,0,1,1,1,1,1,1,1],
[0,0,0,0,0,0,0,0,1,0,1,0,1,0,0,0,0,0,0,0,0],
[1,0,1,0,1,0,1,0,0,0,1,1,0,1,0,1,0,1,1,0,1],
[0,1,0,1,0,1,0,1,1,0,0,1,0,1,0,1,1,0,0,1,0],
[0,0,1,0,0,1,0,1,0,1,0,1,0,1,0,0,1,0,0,0,0],
[1,1,0,1,0,0,1,0,1,0,1,0,1,0,1,1,0,1,1,1,1],
[0,0,0,1,1,1,0,1,0,1,0,1,0,1,1,0,1,1,0,0,0],
[1,1,1,0,0,0,1,0,0,0,1,0,0,1,0,1,1,0,1,1,0],
[1,0,1,1,1,0,1,0,1,1,0,1,0,1,0,0,0,0,0,1,0],
[1,0,0,0,0,0,1,0,0,1,1,0,1,0,1,1,1,0,1,0,1],
[1,1,1,1,1,1,1,0,1,0,1,0,1,1,0,1,0,1,0,0,1],
[0,0,0,0,0,0,0,0,1,0,1,1,0,1,0,1,1,0,1,1,0],
[1,1,1,1,1,1,1,0,0,1,0,1,0,0,0,1,1,0,0,0,0],
[1,0,0,0,0,0,1,0,1,0,1,0,1,0,1,1,0,1,1,1,1],
[1,1,1,1,1,1,1,0,1,0,1,1,0,0,1,0,0,0,0,0,0],
];
const png = createPNG(matrix);
fs.writeFileSync('/home/node/.openclaw/workspace/assets/qr-test.png', png);
console.log('PNG written:', png.length, 'bytes');