Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 | 1704x 1704x 3x 1701x 1x 1700x 1062x 7511x 1214060x 3303256x 3265939x 337x 2x 10009x 2x 13x 731488x 13x 160x 160x 4825x 151789x 160x 5x 5x 12x 30x 12x 5x 7x 15x 15x 7x 6x 6x 6x 14x 1x 13x 37x 6x | /**
* A rectangular grid of bits — the currency of every stage after
* binarization, and of the sampled module grid the decoder reads.
*
* One byte per bit rather than a packed `Uint32Array`: the whole pipeline is
* random-access (finder cross-checks walk diagonals, the sampler jumps to
* arbitrary module centres), and byte indexing keeps that branch-free. A
* 800 × 800 working frame costs 640 KB, which is reused across frames rather
* than reallocated.
*/
export class BitMatrix {
readonly data: Uint8Array;
constructor(readonly width: number, readonly height: number, data?: Uint8Array) {
if (!Number.isInteger(width) || !Number.isInteger(height) || width < 1 || height < 1) {
throw new RangeError(`BitMatrix dimensions must be positive integers, got ${width}×${height}`);
}
if (data && data.length !== width * height) {
throw new RangeError(
`BitMatrix data must be ${width * height} bytes for ${width}×${height}, got ${data.length}`,
);
}
this.data = data ?? new Uint8Array(width * height);
}
/** A square matrix — what a sampled module grid always is. */
static square(size: number, data?: Uint8Array): BitMatrix {
return new BitMatrix(size, size, data);
}
/** Modules per side. Only meaningful for a square (module-grid) matrix. */
get size(): number {
return this.width;
}
get(x: number, y: number): boolean {
return this.data[y * this.width + x] === 1;
}
/** `get` for coordinates that may fall outside the image; outside reads light. */
getSafe(x: number, y: number): boolean {
if (x < 0 || y < 0 || x >= this.width || y >= this.height) return false;
return this.data[y * this.width + x] === 1;
}
set(x: number, y: number, value: boolean): void {
this.data[y * this.width + x] = value ? 1 : 0;
}
/** Count of dark bits — used by the detector to reject empty regions cheaply. */
countDark(): number {
let count = 0;
for (let i = 0; i < this.data.length; i++) count += this.data[i]!;
return count;
}
/**
* A copy with every bit flipped — the inverted pass (SPEC §3.7 `invert`).
* Inverting the *bitmap* rather than re-binarizing the luminance keeps the
* expensive half of the pipeline (thresholding) to one run per frame.
*/
inverted(): BitMatrix {
const flipped = new Uint8Array(this.data.length);
for (let i = 0; i < this.data.length; i++) flipped[i] = this.data[i]! ^ 1;
return new BitMatrix(this.width, this.height, flipped);
}
/**
* A transposed copy — reading a mirrored symbol.
*
* A mirrored QR is the original reflected across its main diagonal, so
* transposing the sampled module grid turns it back into a readable symbol
* without re-running detection.
*/
transposed(): BitMatrix {
const out = new Uint8Array(this.data.length);
for (let y = 0; y < this.height; y++) {
for (let x = 0; x < this.width; x++) {
out[x * this.height + y] = this.data[y * this.width + x]!;
}
}
return new BitMatrix(this.height, this.width, out);
}
/**
* Multi-line debug rendering (`#` dark, `.` light). Used by tests and by
* anyone diagnosing a sampling bug — a grid you can read beats a hex dump.
*/
toString(): string {
const rows: string[] = [];
for (let y = 0; y < this.height; y++) {
let row = "";
for (let x = 0; x < this.width; x++) row += this.data[y * this.width + x] === 1 ? "#" : ".";
rows.push(row);
}
return rows.join("\n");
}
/** Parse the `toString` format — the ergonomic way to write a fixture. */
static fromString(text: string): BitMatrix {
const rows = text
.split("\n")
.map((row) => row.trim())
.filter((row) => row.length > 0);
if (rows.length === 0) throw new RangeError("Cannot build a BitMatrix from empty text");
const width = rows[0]!.length;
const matrix = new BitMatrix(width, rows.length);
rows.forEach((row, y) => {
if (row.length !== width) {
throw new RangeError(`Row ${y} is ${row.length} wide, expected ${width}`);
}
for (let x = 0; x < width; x++) {
matrix.set(x, y, row[x] === "#" || row[x] === "1");
}
});
return matrix;
}
}
|