67 lines4.3 KBJavaScript
1// a QR code for a short text: byte mode, version 2 (25x25), error correction M, mask 0
2// (after Project Nayuki's reference QR generator). node tools/qr.mjs https://ps3.world/contact data/qr-contact.png 12
3import { createRequire } from 'node:module';
4const sharp = createRequire(new URL('../generator/', import.meta.url))('sharp');
5const [text, out, px = '12'] = process.argv.slice(2);
6const VER = 2, SIZE = 17 + 4 * VER, DATA_CW = 28, EC_CW = 16; // version 2-M: one block, 28 data + 16 ec
7
8// ---- the data codewords ----
9const bytes = [...Buffer.from(text, 'utf8')];
10if (bytes.length > 26) throw new Error('too long for version 2-M (26 bytes)');
11const bits = [];
12const put = (v, n) => { for (let i = n - 1; i >= 0; i--) bits.push((v >>> i) & 1); };
13put(0b0100, 4); // byte mode
14put(bytes.length, 8);
15for (const b of bytes) put(b, 8);
16put(0, Math.min(4, DATA_CW * 8 - bits.length)); // terminator
17while (bits.length % 8) bits.push(0);
18const data = [];
19for (let i = 0; i < bits.length; i += 8) data.push(bits.slice(i, i + 8).reduce((a, b) => a * 2 + b, 0));
20for (let pad = 0xec; data.length < DATA_CW; pad ^= 0xec ^ 0x11) data.push(pad);
21
22// ---- Reed-Solomon over GF(256), 0x11D ----
23const mul = (x, y) => { let z = 0; for (let i = 7; i >= 0; i--) { z = (z << 1) ^ ((z >>> 7) * 0x11d); z ^= ((y >>> i) & 1) * x; } return z; };
24const divisor = (() => { const r = new Array(EC_CW).fill(0); r[EC_CW - 1] = 1; let root = 1;
25 for (let i = 0; i < EC_CW; i++) { for (let j = 0; j < r.length; j++) { r[j] = mul(r[j], root); if (j + 1 < r.length) r[j] ^= r[j + 1]; } root = mul(root, 2); } return r; })();
26const ec = (() => { const r = divisor.map(() => 0);
27 for (const b of data) { const f = b ^ r.shift(); r.push(0); divisor.forEach((c, i) => { r[i] ^= mul(c, f); }); } return r; })();
28const codewords = [...data, ...ec];
29
30// ---- the matrix: [y][x] ----
31const mod = Array.from({ length: SIZE }, () => new Array(SIZE).fill(false));
32const fn = Array.from({ length: SIZE }, () => new Array(SIZE).fill(false));
33const set = (x, y, dark) => { mod[y][x] = dark; fn[y][x] = true; };
34for (let i = 0; i < SIZE; i++) { set(6, i, i % 2 === 0); set(i, 6, i % 2 === 0); } // timing
35const finder = (cx, cy) => { for (let dy = -4; dy <= 4; dy++) for (let dx = -4; dx <= 4; dx++) {
36 const d = Math.max(Math.abs(dx), Math.abs(dy)), x = cx + dx, y = cy + dy;
37 if (x >= 0 && x < SIZE && y >= 0 && y < SIZE) set(x, y, d !== 2 && d !== 4); } };
38finder(3, 3); finder(SIZE - 4, 3); finder(3, SIZE - 4);
39for (let dy = -2; dy <= 2; dy++) for (let dx = -2; dx <= 2; dx++) set(18 + dx, 18 + dy, Math.max(Math.abs(dx), Math.abs(dy)) !== 1); // alignment (v2)
40// format: level M (0), mask 0
41const fdata = (0 << 3) | 0; let rem = fdata;
42for (let i = 0; i < 10; i++) rem = (rem << 1) ^ ((rem >>> 9) * 0x537);
43const fbits = ((fdata << 10) | rem) ^ 0x5412, fb = i => ((fbits >>> i) & 1) === 1;
44for (let i = 0; i <= 5; i++) set(8, i, fb(i));
45set(8, 7, fb(6)); set(8, 8, fb(7)); set(7, 8, fb(8));
46for (let i = 9; i < 15; i++) set(14 - i, 8, fb(i));
47for (let i = 0; i < 8; i++) set(SIZE - 1 - i, 8, fb(i));
48for (let i = 8; i < 15; i++) set(8, SIZE - 15 + i, fb(i));
49set(8, SIZE - 8, true); // the dark module
50
51// data, in the zigzag; then mask 0 ((x + y) even flips) on all but the function modules
52let k = 0;
53for (let right = SIZE - 1; right >= 1; right -= 2) {
54 if (right === 6) right = 5;
55 for (let v = 0; v < SIZE; v++) for (let j = 0; j < 2; j++) {
56 const x = right - j, up = ((right + 1) & 2) === 0, y = up ? SIZE - 1 - v : v;
57 if (!fn[y][x] && k < codewords.length * 8) { mod[y][x] = ((codewords[k >>> 3] >>> (7 - (k & 7))) & 1) === 1; k++; }
58 }
59}
60for (let y = 0; y < SIZE; y++) for (let x = 0; x < SIZE; x++) if (!fn[y][x] && (x + y) % 2 === 0) mod[y][x] = !mod[y][x];
61
62// ---- the picture: black on white, a 4-module quiet zone ----
63const P = +px, Q = 4, W = (SIZE + 2 * Q) * P;
64let rects = '';
65for (let y = 0; y < SIZE; y++) for (let x = 0; x < SIZE; x++) if (mod[y][x]) rects += `<rect x="${(x + Q) * P}" y="${(y + Q) * P}" width="${P}" height="${P}"/>`;
66await sharp(Buffer.from(`<svg xmlns="http://www.w3.org/2000/svg" width="${W}" height="${W}" shape-rendering="crispEdges"><rect width="${W}" height="${W}" fill="#fff"/><g fill="#000">${rects}</g></svg>`)).png().toFile(out);
67console.log(`${out}: ${SIZE}x${SIZE} modules, ${W}x${W} px, "${text}" (${bytes.length} bytes)`);