Skip to content

barcodes: QR and Code 128 in pure Rip - #306

Merged
shreeve merged 5 commits into
mainfrom
barcodes
Sep 16, 2026
Merged

shreeve merged 5 commits into
mainfrom
barcodes

Conversation

@shreeve

@shreeve shreeve commented Sep 16, 2026

Copy link
Copy Markdown
Owner

Summary

A new packages/barcodes package: a pure-Rip port of Paul Miller's qr 0.7.0 (encoder, camera-budgeted decoder, canvas and camera plumbing) plus a Code 128 encoder and reader, zero dependencies, browser-safe.

  • qr.rip holds the QR encoder, decoder and QRScanner. The encoder is byte-identical to the reference on 1452 checks across every version, level, mask and output; the decoder is identical on 192 synthetic checks across rotations, inversion, input formats and batch decoding.
  • code128.rip holds both sides of ISO/IEC 15417: shortest-sequence subset selection by dynamic programming, GS1-128 FNC1, the same six outputs, and a scan-line reader that handles both directions, both axes and inverted symbols.
  • barcodes.rip is the root entry re-exporting both symbologies; gif.rip and image.rip are the writer and image-input helpers they share; dom.rip is the canvas, camera and BarcodeDetector layer.

Performance

Under Bun against the reference TypeScript, best of three:

case rip reference delta
encode raw v8 17.8 µs 18.7 µs -5%
encode svg v8 43.9 µs 47.9 µs -8%
decode small raster 39.7 µs 115.9 µs -66%
decode 720p 660 µs 1.02 ms -35%
decode 1080p 1.50 ms 2.25 ms -33%
decode 1080p miss 23.7 ms 24.4 ms -3%

The decoder wins come from reading packed pixels as whole words in the luma conversion and the pyramid box filter, and from creating payload views on first use instead of one per possible length at construction. Decoder output is unchanged.

Test

bun run test in packages/barcodes: 33 tests pinning spec tables, codewords, every output, every version and mask, decoder round trips, Code 128 vectors and round trips, and the package surface including browser safety.

Port of paulmillr/qr 0.7.0 (MIT OR Apache-2.0, ZXing-derived) as
packages/barcodes: spec.rip (ISO/IEC 18004 tables, GF(2^8), BCH and
Golay words), barcodes.rip (encodeQR, packed-bitmap encoder with the
word-parallel mask race), decode.rip (decodeQR, decodeQRBatch, QRScanner
with the zero-allocation frame path) and dom.rip (QRCanvas, QRCamera,
frameLoop, svgToPng, gifToPng, BarcodeDetector).

Labeled blocks became named methods and delegated generators; the shared
scratch arena became named arrays. Encoder output is byte-identical to
the reference on 1452 checks across every version, level, mask and
output; the decoder matches on 192 synthetic checks. Under Bun the
encoder is equal or faster and the decoder within noise.

test.rip pins spec tables, codewords, every version and mask against
vectors generated from the reference, then round-trips the decoder over
rotations, inverted symbols, input formats, batch decoding and scanner
reuse.
Add code128.rip with both sides of ISO/IEC 15417 in one file: an encoder
that picks the shortest codeword sequence over subsets A, B and C by
dynamic programming, with GS1-128 FNC1 and the six QR outputs, and a
scan-line reader that matches each eleven-module group to its nearest
codeword in both directions, on both axes and in both polarities, then
verifies stop, termination bar and the mod-103 check.

Lift the GIF87a writer into gif.rip and the image validation and luma
conversion into image.rip so both symbologies share them. Re-export the
Code 128 entry points from the encoder and decoder entries.

Replace every def declaration with the standard name =! (args) -> form;
void procedures ending in a loop return explicitly so the arrow form
does not collect. Emitted JS, parity oracles and benchmarks are unchanged.

Tests: 33 passing, with a Code 128 section pinning the published vector,
subset choices, every output, and round trips across scales, rotations,
inversion, luma input and GS1-128.
Merge the QR encoder and decoder into qr.rip beside code128.rip, and
reduce barcodes.rip to the root entry that re-exports both symbologies.
Subpaths are ./qr, ./code128 and ./dom.

Every module-level helper in qr.rip is a const binding: the merge exposed
an encoder local silently overwriting the decoder's plain-assigned GF
multiply, and a const turns any such collision into a compile error.

Code 128 validates its arguments through the same asString, asObject and
asInt helpers the QR encoder uses.

Tests 33/33, parity oracles unchanged, bench unchanged.
Read packed four-byte pixels as whole words in the luma conversion, one
load per pixel unrolled four wide; the (r + 2g + b) / 4 weights are
symmetric in r and b, so every RGBA/BGRA/X layout takes the path. Sum the
2x2 pyramid box filter in two 16-bit lanes per word. Both gate on little
endian and alignment and keep the byte loops as fallback.

Create payload byte views on first use instead of one per possible length
at construction, and test the finder ratio in the scan loop before
calling recordFinder.

Decoder results are unchanged: parity oracles report zero mismatches.
Against the reference implementation under Bun the decoder now runs 33%
faster at 1080p, 35% at 720p and 66% on a small raster.
@shreeve
shreeve merged commit 5e07fdb into main Sep 16, 2026
2 checks passed
@shreeve
shreeve deleted the barcodes branch September 16, 2026 06:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant