bili-transcript 0.0.0-stage → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +135 -2
- package/SKILL.md +84 -0
- package/package.json +43 -4
- package/src/api.mjs +74 -0
- package/src/auth.mjs +45 -0
- package/src/cli.mjs +162 -0
- package/src/doctor.mjs +83 -0
- package/src/fetch.mjs +128 -0
- package/src/install-skill.mjs +63 -0
- package/src/login.mjs +116 -0
- package/src/qr.mjs +402 -0
package/src/qr.mjs
ADDED
|
@@ -0,0 +1,402 @@
|
|
|
1
|
+
import { writeFileSync as _wfs } from 'node:fs';
|
|
2
|
+
import { deflateSync } from 'node:zlib';
|
|
3
|
+
// 纯 JS 二维码编码器 —— 零依赖
|
|
4
|
+
// 支持:字节模式、纠错等级 M、版本 1–10、自动选版本、掩码自动择优
|
|
5
|
+
// 正确性由 tests/qr-verify 与 Python qrcode 库逐模块比对验证
|
|
6
|
+
|
|
7
|
+
// ---------- GF(256) ----------
|
|
8
|
+
const EXP = new Uint8Array(512), LOG = new Uint8Array(256);
|
|
9
|
+
(() => {
|
|
10
|
+
let x = 1;
|
|
11
|
+
for (let i = 0; i < 255; i++) { EXP[i] = x; LOG[x] = i; x <<= 1; if (x & 0x100) x ^= 0x11d; }
|
|
12
|
+
for (let i = 255; i < 512; i++) EXP[i] = EXP[i - 255];
|
|
13
|
+
})();
|
|
14
|
+
const gmul = (a, b) => (a === 0 || b === 0 ? 0 : EXP[LOG[a] + LOG[b]]);
|
|
15
|
+
|
|
16
|
+
function rsGenPoly(n) {
|
|
17
|
+
let p = [1];
|
|
18
|
+
for (let i = 0; i < n; i++) {
|
|
19
|
+
const q = [1, EXP[i]];
|
|
20
|
+
const r = new Array(p.length + 1).fill(0);
|
|
21
|
+
for (let a = 0; a < p.length; a++)
|
|
22
|
+
for (let b = 0; b < 2; b++) r[a + b] ^= gmul(p[a], q[b]);
|
|
23
|
+
p = r;
|
|
24
|
+
}
|
|
25
|
+
return p;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function rsEncode(data, ecLen) {
|
|
29
|
+
const gen = rsGenPoly(ecLen);
|
|
30
|
+
const res = new Array(data.length + ecLen).fill(0);
|
|
31
|
+
data.forEach((v, i) => (res[i] = v));
|
|
32
|
+
for (let i = 0; i < data.length; i++) {
|
|
33
|
+
const coef = res[i];
|
|
34
|
+
if (coef === 0) continue;
|
|
35
|
+
for (let j = 0; j < gen.length; j++) res[i + j] ^= gmul(gen[j], coef);
|
|
36
|
+
}
|
|
37
|
+
return res.slice(data.length);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// ---------- 版本表(纠错等级 M,版本 1–10)----------
|
|
41
|
+
// [ [块数, 每块总码字, 每块数据码字], ... ]
|
|
42
|
+
const RS_M = {
|
|
43
|
+
1: [[1, 26, 16]],
|
|
44
|
+
2: [[1, 44, 28]],
|
|
45
|
+
3: [[1, 70, 44]],
|
|
46
|
+
4: [[2, 50, 32]],
|
|
47
|
+
5: [[2, 67, 43]],
|
|
48
|
+
6: [[4, 43, 27]],
|
|
49
|
+
7: [[4, 49, 31]],
|
|
50
|
+
8: [[2, 60, 38], [2, 61, 39]],
|
|
51
|
+
9: [[3, 58, 36], [2, 59, 37]],
|
|
52
|
+
10: [[4, 69, 43], [1, 70, 44]],
|
|
53
|
+
};
|
|
54
|
+
const ALIGN = { 1: [], 2: [6, 18], 3: [6, 22], 4: [6, 26], 5: [6, 30],
|
|
55
|
+
6: [6, 34], 7: [6, 22, 38], 8: [6, 24, 42], 9: [6, 26, 46], 10: [6, 28, 50] };
|
|
56
|
+
|
|
57
|
+
function dataCapacity(v) {
|
|
58
|
+
return RS_M[v].reduce((s, [n, , d]) => s + n * d, 0);
|
|
59
|
+
}
|
|
60
|
+
function ccBits(v) { return v <= 9 ? 8 : 16; } // 字节模式字符计数指示符位数
|
|
61
|
+
|
|
62
|
+
// ---------- 比特流 ----------
|
|
63
|
+
class Bits {
|
|
64
|
+
constructor() { this.arr = []; }
|
|
65
|
+
put(val, len) { for (let i = len - 1; i >= 0; i--) this.arr.push((val >>> i) & 1); }
|
|
66
|
+
get length() { return this.arr.length; }
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function encodeData(bytes, v) {
|
|
70
|
+
const cap = dataCapacity(v) * 8;
|
|
71
|
+
const b = new Bits();
|
|
72
|
+
b.put(0b0100, 4); // 字节模式
|
|
73
|
+
b.put(bytes.length, ccBits(v)); // 字符数
|
|
74
|
+
for (const x of bytes) b.put(x, 8);
|
|
75
|
+
// 终止符
|
|
76
|
+
const term = Math.min(4, cap - b.length);
|
|
77
|
+
b.put(0, term);
|
|
78
|
+
// 补齐到字节
|
|
79
|
+
while (b.length % 8) b.arr.push(0);
|
|
80
|
+
// 填充码字
|
|
81
|
+
const pads = [0xec, 0x11];
|
|
82
|
+
let i = 0;
|
|
83
|
+
while (b.length < cap) { b.put(pads[i++ % 2], 8); }
|
|
84
|
+
// 转成码字
|
|
85
|
+
const out = [];
|
|
86
|
+
for (let k = 0; k < b.length; k += 8) {
|
|
87
|
+
let byte = 0;
|
|
88
|
+
for (let j = 0; j < 8; j++) byte = (byte << 1) | b.arr[k + j];
|
|
89
|
+
out.push(byte);
|
|
90
|
+
}
|
|
91
|
+
return out;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function interleave(dataCodewords, v) {
|
|
95
|
+
const blocks = [];
|
|
96
|
+
let off = 0;
|
|
97
|
+
for (const [n, tot, dat] of RS_M[v]) {
|
|
98
|
+
for (let i = 0; i < n; i++) {
|
|
99
|
+
const d = dataCodewords.slice(off, off + dat); off += dat;
|
|
100
|
+
blocks.push({ d, e: rsEncode(d, tot - dat) });
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
const out = [];
|
|
104
|
+
const maxD = Math.max(...blocks.map((b) => b.d.length));
|
|
105
|
+
for (let i = 0; i < maxD; i++) for (const b of blocks) if (i < b.d.length) out.push(b.d[i]);
|
|
106
|
+
const maxE = Math.max(...blocks.map((b) => b.e.length));
|
|
107
|
+
for (let i = 0; i < maxE; i++) for (const b of blocks) if (i < b.e.length) out.push(b.e[i]);
|
|
108
|
+
return out;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// ---------- 矩阵 ----------
|
|
112
|
+
function newMatrix(size) {
|
|
113
|
+
return Array.from({ length: size }, () => new Array(size).fill(null));
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function placeFinder(m, r, c) {
|
|
117
|
+
for (let i = -1; i <= 7; i++) for (let j = -1; j <= 7; j++) {
|
|
118
|
+
const y = r + i, x = c + j;
|
|
119
|
+
if (y < 0 || x < 0 || y >= m.length || x >= m.length) continue;
|
|
120
|
+
const on = (i >= 0 && i <= 6 && (j === 0 || j === 6)) ||
|
|
121
|
+
(j >= 0 && j <= 6 && (i === 0 || i === 6)) ||
|
|
122
|
+
(i >= 2 && i <= 4 && j >= 2 && j <= 4);
|
|
123
|
+
m[y][x] = on;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function placeAlignment(m, size, v) {
|
|
128
|
+
const pos = ALIGN[v];
|
|
129
|
+
for (const r of pos) for (const c of pos) {
|
|
130
|
+
if ((r === 6 && c === 6) || (r === 6 && c === size - 7) || (r === size - 7 && c === 6)) continue;
|
|
131
|
+
for (let i = -2; i <= 2; i++) for (let j = -2; j <= 2; j++) {
|
|
132
|
+
m[r + i][c + j] = Math.max(Math.abs(i), Math.abs(j)) !== 1;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function reservedMask(size, v) {
|
|
138
|
+
const r = Array.from({ length: size }, () => new Array(size).fill(false));
|
|
139
|
+
const mark = (rr, cc) => { if (rr >= 0 && cc >= 0 && rr < size && cc < size) r[rr][cc] = true; };
|
|
140
|
+
// 定位图形 + 分隔符(锚点必须是 n-7,不是 n-8)
|
|
141
|
+
for (const [y, x] of [[0, 0], [0, size - 7], [size - 7, 0]]) {
|
|
142
|
+
for (let i = -1; i <= 7; i++) for (let j = -1; j <= 7; j++) mark(y + i, x + j);
|
|
143
|
+
}
|
|
144
|
+
// 定时图形
|
|
145
|
+
for (let i = 0; i < size; i++) { mark(6, i); mark(i, 6); }
|
|
146
|
+
// 校正图形
|
|
147
|
+
for (const rr of ALIGN[v]) for (const cc of ALIGN[v]) {
|
|
148
|
+
if ((rr === 6 && cc === 6) || (rr === 6 && cc === size - 7) || (rr === size - 7 && cc === 6)) continue;
|
|
149
|
+
for (let i = -2; i <= 2; i++) for (let j = -2; j <= 2; j++) mark(rr + i, cc + j);
|
|
150
|
+
}
|
|
151
|
+
// 格式信息
|
|
152
|
+
for (let i = 0; i <= 8; i++) { mark(8, i); mark(i, 8); }
|
|
153
|
+
for (let i = 0; i < 8; i++) { mark(8, size - 1 - i); mark(size - 1 - i, 8); }
|
|
154
|
+
// 版本信息(v>=7)
|
|
155
|
+
if (v >= 7) {
|
|
156
|
+
for (let i = 0; i < 6; i++) for (let j = 0; j < 3; j++) { mark(size - 11 + j, i); mark(i, size - 11 + j); }
|
|
157
|
+
}
|
|
158
|
+
return r;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function buildMatrix(v, codewords) {
|
|
162
|
+
const size = 4 * v + 17;
|
|
163
|
+
const m = newMatrix(size);
|
|
164
|
+
const res = reservedMask(size, v);
|
|
165
|
+
placeFinder(m, 0, 0); placeFinder(m, 0, size - 7); placeFinder(m, size - 7, 0);
|
|
166
|
+
placeAlignment(m, size, v);
|
|
167
|
+
for (let i = 8; i < size - 8; i++) { m[6][i] = i % 2 === 0; m[i][6] = i % 2 === 0; }
|
|
168
|
+
m[size - 8][8] = true; // 固定暗模块
|
|
169
|
+
|
|
170
|
+
// 数据按之字形填入
|
|
171
|
+
const bits = [];
|
|
172
|
+
for (const cw of codewords) for (let i = 7; i >= 0; i--) bits.push((cw >>> i) & 1);
|
|
173
|
+
let bi = 0, up = true;
|
|
174
|
+
for (let col = size - 1; col > 0; col -= 2) {
|
|
175
|
+
if (col === 6) col--;
|
|
176
|
+
for (let k = 0; k < size; k++) {
|
|
177
|
+
const row = up ? size - 1 - k : k;
|
|
178
|
+
for (const c of [col, col - 1]) {
|
|
179
|
+
if (res[row][c]) continue;
|
|
180
|
+
m[row][c] = bi < bits.length ? bits[bi++] === 1 : false;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
up = !up;
|
|
184
|
+
}
|
|
185
|
+
return { m, res, size };
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function maskFn(id, r, c) {
|
|
189
|
+
switch (id) {
|
|
190
|
+
case 0: return (r + c) % 2 === 0;
|
|
191
|
+
case 1: return r % 2 === 0;
|
|
192
|
+
case 2: return c % 3 === 0;
|
|
193
|
+
case 3: return (r + c) % 3 === 0;
|
|
194
|
+
case 4: return (Math.floor(r / 2) + Math.floor(c / 3)) % 2 === 0;
|
|
195
|
+
case 5: return ((r * c) % 2) + ((r * c) % 3) === 0;
|
|
196
|
+
case 6: return (((r * c) % 2) + ((r * c) % 3)) % 2 === 0;
|
|
197
|
+
case 7: return (((r + c) % 2) + ((r * c) % 3)) % 2 === 0;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function penalty(m, size) {
|
|
202
|
+
let p = 0;
|
|
203
|
+
// 规则 1:同色连续
|
|
204
|
+
for (let r = 0; r < size; r++) {
|
|
205
|
+
let run = 1;
|
|
206
|
+
for (let c = 1; c < size; c++) {
|
|
207
|
+
if (m[r][c] === m[r][c - 1]) run++; else { if (run >= 5) p += run - 2; run = 1; }
|
|
208
|
+
}
|
|
209
|
+
if (run >= 5) p += run - 2;
|
|
210
|
+
}
|
|
211
|
+
for (let c = 0; c < size; c++) {
|
|
212
|
+
let run = 1;
|
|
213
|
+
for (let r = 1; r < size; r++) {
|
|
214
|
+
if (m[r][c] === m[r - 1][c]) run++; else { if (run >= 5) p += run - 2; run = 1; }
|
|
215
|
+
}
|
|
216
|
+
if (run >= 5) p += run - 2;
|
|
217
|
+
}
|
|
218
|
+
// 规则 2:2x2 同色块
|
|
219
|
+
for (let r = 0; r < size - 1; r++) for (let c = 0; c < size - 1; c++) {
|
|
220
|
+
const v = m[r][c];
|
|
221
|
+
if (v === m[r][c + 1] && v === m[r + 1][c] && v === m[r + 1][c + 1]) p += 3;
|
|
222
|
+
}
|
|
223
|
+
// 规则 3:1:1:3:1:1 图形
|
|
224
|
+
const pat = [true, false, true, true, true, false, true];
|
|
225
|
+
const check = (get) => {
|
|
226
|
+
for (let i = 0; i + 7 <= size; i++) {
|
|
227
|
+
let hit = true;
|
|
228
|
+
for (let j = 0; j < 7; j++) if (get(i + j) !== pat[j]) { hit = false; break; }
|
|
229
|
+
if (!hit) continue;
|
|
230
|
+
const before = [i - 4, i - 3, i - 2, i - 1];
|
|
231
|
+
const after = [i + 7, i + 8, i + 9, i + 10];
|
|
232
|
+
const allQuiet = (idx) => idx.every((k) => k < 0 || k >= size || get(k) === false);
|
|
233
|
+
if (allQuiet(before) || allQuiet(after)) p += 40;
|
|
234
|
+
}
|
|
235
|
+
};
|
|
236
|
+
for (let r = 0; r < size; r++) check((i) => m[r][i]);
|
|
237
|
+
for (let c = 0; c < size; c++) check((i) => m[i][c]);
|
|
238
|
+
// 规则 4:暗模块比例
|
|
239
|
+
let dark = 0;
|
|
240
|
+
for (let r = 0; r < size; r++) for (let c = 0; c < size; c++) if (m[r][c]) dark++;
|
|
241
|
+
const ratio = (dark * 100) / (size * size);
|
|
242
|
+
p += Math.floor(Math.abs(ratio - 50) / 5) * 10;
|
|
243
|
+
return p;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
const BCH_FMT = 0x537, BCH_VER = 0x1f25;
|
|
247
|
+
function bch(data, gen, genBits) {
|
|
248
|
+
let d = data << (genBits - 1);
|
|
249
|
+
while (Math.floor(Math.log2(d)) >= genBits - 1) d ^= gen << (Math.floor(Math.log2(d)) - (genBits - 1));
|
|
250
|
+
return ((data << (genBits - 1)) | d);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function applyFormat(m, size, ecBits, maskId) {
|
|
254
|
+
const data = (ecBits << 3) | maskId;
|
|
255
|
+
const f = bch(data, BCH_FMT, 11) ^ 0x5412;
|
|
256
|
+
const bit = (i) => ((f >>> i) & 1) === 1;
|
|
257
|
+
// 垂直(列 8):b0-b5 在行 0-5,b6/b7 在行 7/8,b8-b14 在行 n-7..n-1
|
|
258
|
+
for (let i = 0; i < 15; i++) {
|
|
259
|
+
const v = bit(i);
|
|
260
|
+
if (i < 6) m[i][8] = v;
|
|
261
|
+
else if (i < 8) m[i + 1][8] = v;
|
|
262
|
+
else m[size - 15 + i][8] = v;
|
|
263
|
+
}
|
|
264
|
+
// 水平(行 8):b0-b7 在列 n-1..n-8,b8 在列 7,b9-b14 在列 5..0
|
|
265
|
+
for (let i = 0; i < 15; i++) {
|
|
266
|
+
const v = bit(i);
|
|
267
|
+
if (i < 8) m[8][size - i - 1] = v;
|
|
268
|
+
else if (i < 9) m[8][7] = v;
|
|
269
|
+
else m[8][14 - i] = v;
|
|
270
|
+
}
|
|
271
|
+
m[size - 8][8] = true; // 固定暗模块
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
function applyVersionInfo(m, size, v) {
|
|
275
|
+
if (v < 7) return;
|
|
276
|
+
let d = v;
|
|
277
|
+
let rem = v;
|
|
278
|
+
for (let i = 0; i < 12; i++) {
|
|
279
|
+
rem = (rem << 1) ^ ((rem >>> 11) * BCH_VER);
|
|
280
|
+
}
|
|
281
|
+
const bits = (d << 12) | rem;
|
|
282
|
+
for (let i = 0; i < 18; i++) {
|
|
283
|
+
const b = ((bits >>> i) & 1) === 1;
|
|
284
|
+
const r = Math.floor(i / 3), c = i % 3;
|
|
285
|
+
m[size - 11 + c][r] = b;
|
|
286
|
+
m[r][size - 11 + c] = b;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/** 编码:返回二维布尔数组(true = 深色) */
|
|
291
|
+
export function encodeQR(text, { ecc = 'M', mask: forcedMask = null } = {}) {
|
|
292
|
+
if (ecc !== 'M') throw new Error('只支持纠错等级 M');
|
|
293
|
+
const bytes = [...Buffer.from(text, 'utf8')];
|
|
294
|
+
let v = 1;
|
|
295
|
+
// 头部位数:模式(4) + 计数(ccBits) ;需要字节对齐后的总位数 <= 容量
|
|
296
|
+
for (v = 1; v <= 10; v++) {
|
|
297
|
+
const need = 4 + ccBits(v) + bytes.length * 8;
|
|
298
|
+
if (need <= dataCapacity(v) * 8) break;
|
|
299
|
+
}
|
|
300
|
+
if (v > 10) throw new Error('内容太长(超过版本 10 容量)');
|
|
301
|
+
|
|
302
|
+
const dataCw = encodeData(bytes, v);
|
|
303
|
+
const all = interleave(dataCw, v);
|
|
304
|
+
const { m, res, size } = buildMatrix(v, all);
|
|
305
|
+
|
|
306
|
+
// 掩码择优(forcedMask 用于测试)
|
|
307
|
+
let best = null, bestScore = Infinity;
|
|
308
|
+
for (let mask = 0; mask < 8; mask++) {
|
|
309
|
+
if (forcedMask !== null && mask !== forcedMask) continue;
|
|
310
|
+
const t = m.map((row) => row.slice());
|
|
311
|
+
for (let r = 0; r < size; r++) for (let c = 0; c < size; c++) {
|
|
312
|
+
if (res[r][c]) continue;
|
|
313
|
+
if (maskFn(mask, r, c)) t[r][c] = !t[r][c];
|
|
314
|
+
}
|
|
315
|
+
applyVersionInfo(t, size, v);
|
|
316
|
+
applyFormat(t, size, 0, mask); // 纠错等级 M 的 ecBits = 0
|
|
317
|
+
const s = penalty(t, size);
|
|
318
|
+
if (s < bestScore) { bestScore = s; best = t; }
|
|
319
|
+
}
|
|
320
|
+
return best;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/** 渲染成文本(两个字符一行,便于终端显示) */
|
|
324
|
+
export function renderText(matrix, dark = '██', light = ' ') {
|
|
325
|
+
return matrix.map((row) => row.map((b) => (b ? dark : light)).join('')).join('\n');
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// ---------- 极简 PNG 写出(1 位灰度,零依赖)----------
|
|
329
|
+
const CRC_TABLE = (() => {
|
|
330
|
+
const t = new Int32Array(256);
|
|
331
|
+
for (let n = 0; n < 256; n++) {
|
|
332
|
+
let c = n;
|
|
333
|
+
for (let k = 0; k < 8; k++) c = c & 1 ? 0xedb88320 ^ (c >>> 1) : c >>> 1;
|
|
334
|
+
t[n] = c;
|
|
335
|
+
}
|
|
336
|
+
return t;
|
|
337
|
+
})();
|
|
338
|
+
function crc32(buf) {
|
|
339
|
+
let c = -1;
|
|
340
|
+
for (let i = 0; i < buf.length; i++) c = CRC_TABLE[(c ^ buf[i]) & 0xff] ^ (c >>> 8);
|
|
341
|
+
return (c ^ -1) >>> 0;
|
|
342
|
+
}
|
|
343
|
+
function chunk(type, data) {
|
|
344
|
+
const len = Buffer.alloc(4); len.writeUInt32BE(data.length);
|
|
345
|
+
const td = Buffer.concat([Buffer.from(type, 'ascii'), data]);
|
|
346
|
+
const crc = Buffer.alloc(4); crc.writeUInt32BE(crc32(td));
|
|
347
|
+
return Buffer.concat([len, td, crc]);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/** 把矩阵写成 PNG 文件(白底黑点,带静默区) */
|
|
351
|
+
export function writePNG(matrix, path, { scale = 8, quiet = 4 } = {}) {
|
|
352
|
+
const n = matrix.length;
|
|
353
|
+
const w = (n + quiet * 2) * scale;
|
|
354
|
+
const h = w;
|
|
355
|
+
const rowBytes = Math.ceil(w / 8);
|
|
356
|
+
const raw = Buffer.alloc((rowBytes + 1) * h, 0xff); // 全白
|
|
357
|
+
for (let y = 0; y < h; y++) raw[y * (rowBytes + 1)] = 0; // 过滤器类型 0
|
|
358
|
+
for (let r = 0; r < n; r++) {
|
|
359
|
+
for (let c = 0; c < n; c++) {
|
|
360
|
+
if (!matrix[r][c]) continue;
|
|
361
|
+
for (let dy = 0; dy < scale; dy++) {
|
|
362
|
+
const y = (r + quiet) * scale + dy;
|
|
363
|
+
const base = y * (rowBytes + 1) + 1;
|
|
364
|
+
for (let dx = 0; dx < scale; dx++) {
|
|
365
|
+
const x = (c + quiet) * scale + dx;
|
|
366
|
+
raw[base + (x >> 3)] &= ~(0x80 >> (x & 7)); // 置为黑
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
const ihdr = Buffer.alloc(13);
|
|
373
|
+
ihdr.writeUInt32BE(w, 0); ihdr.writeUInt32BE(h, 4);
|
|
374
|
+
ihdr[8] = 1; // 位深 1
|
|
375
|
+
ihdr[9] = 0; // 灰度
|
|
376
|
+
const png = Buffer.concat([
|
|
377
|
+
Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]),
|
|
378
|
+
chunk('IHDR', ihdr),
|
|
379
|
+
chunk('IDAT', deflateSync(raw, { level: 9 })),
|
|
380
|
+
chunk('IEND', Buffer.alloc(0)),
|
|
381
|
+
]);
|
|
382
|
+
_wfs(path, png);
|
|
383
|
+
return { width: w, height: h, bytes: png.length };
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
/** 调试用:导出中间产物 */
|
|
387
|
+
export function debugCodewords(text) {
|
|
388
|
+
const bytes = [...Buffer.from(text, 'utf8')];
|
|
389
|
+
let v = 1;
|
|
390
|
+
for (v = 1; v <= 10; v++) {
|
|
391
|
+
if (4 + ccBits(v) + bytes.length * 8 <= dataCapacity(v) * 8) break;
|
|
392
|
+
}
|
|
393
|
+
const dataCw = encodeData(bytes, v);
|
|
394
|
+
return { version: v, dataCw, all: interleave(dataCw, v) };
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
/** 调试用:导出「非数据格」分布 */
|
|
398
|
+
export function debugReserved(version) {
|
|
399
|
+
const size = 4 * version + 17;
|
|
400
|
+
const res = reservedMask(size, version);
|
|
401
|
+
return res.map((row) => row.map((b) => (b ? 1 : 0)).join(''));
|
|
402
|
+
}
|