Skip to content

Commit 418c4ec

Browse files
committed
readme
1 parent 2cbf697 commit 418c4ec

1 file changed

Lines changed: 44 additions & 12 deletions

File tree

README.md

Lines changed: 44 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,11 @@
33
Minimal 0-dependency QR code generator & reader.
44

55
- 🔒 Auditable, 0-dependency
6-
- 🏎 [Fast](#speed): aims to be faster than all JS implementations
7-
- 🔍 Reliable: 100MB+ of extensive test vectors ensure correctness
6+
- 🏎 [Fast](#speed): faster than all JS implementations, faster than native zxing-cpp
7+
- 🔍 Reliable: decodes 63-69% of BoofCV photo vectors; compared to zxing-cpp 50-75% (for fast-slow modes)
88
- 🏞️ Encoding (generating) supports ASCII, term, gif, svg and png codes
99
- 📷 Decoding (reading) supports camera feed input, files and non-browser environments
10-
- 🪶 18KB (gzipped) for encoding + decoding, 9KB for encoding
10+
- 🪶 26KB (gzipped) for encoding + decoding, 10KB for encoding
1111

1212
Check out:
1313

@@ -163,8 +163,10 @@ type DecodeGrid = {
163163
type DecodeOpts = {
164164
cropToSquare?: boolean;
165165
grid?: DecodeGrid; // trusted centered, axis-aligned grid geometry
166-
moreEffort?: boolean;
167-
textDecoder?: (bytes: Uint8Array) => string;
166+
// default true: invert / downscale retries, downscale-first detection.
167+
// 'max' adds slow rotation / crop / upscale fallbacks; false = single pass
168+
multiPass?: boolean | 'max';
169+
textDecoder?: (bytes: Uint8Array, eci?: number) => string;
168170
pointsOnDetect?: (points: FinderPoints) => void;
169171
imageOnBitmap?: (img: Image) => void;
170172
imageOnDetect?: (img: Image) => void;
@@ -175,6 +177,22 @@ declare function decodeQR(img: Image | Bitmap, opts?: DecodeOpts): string;
175177

176178
A `Bitmap` input must contain the exact borderless QR module matrix. For a rendered image whose integer module geometry is already known, `grid` skips photographic binarization and finder detection. Centered generated grids are also recognized automatically when their quiet zone and finder patterns validate.
177179

180+
`multiPass` controls how much work the decoder may spend on one image:
181+
182+
- `true` (default) — adaptive multi-pass detection. Large frames are detected
183+
on a box-downscaled raster first (full resolution stays as the fallback);
184+
failed decodes are retried with inverted colors (light-on-dark codes,
185+
screens) and on 2x..32x downscaled rasters (codes overflowing the frame,
186+
screen moire). Extra passes only run after the primary pass fails, so
187+
images that already decode pay nothing.
188+
- `'max'` — additionally tries rotated, cropped, and upscaled fallback
189+
candidates. Decodes noticeably more hard photos, but can be several times
190+
slower on images that still fail. Good for one-shot file decoding; avoid it
191+
for per-frame camera scanning, where the next frame is usually a better
192+
candidate than a slow retry.
193+
- `false` — strict single-pass decode: one binarization, one detection, no
194+
retries. Deterministic latency for tests and trusted inputs.
195+
178196
### Decoding algorithm
179197

180198
QR code decoding is challenging; it is essentially a computer vision problem. There are two main scenarios:
@@ -186,18 +204,19 @@ The state-of-the-art approach for this, as with other computer vision problems,
186204

187205
The implemented reader algorithm is inspired by [ZXing](https://github.com/zxing/zxing):
188206

189-
1. `toBitmap`: Convert the image to a bitmap of black and white segments. This is the slowest part and the most important.
190-
2. `detect`: Find three finder patterns and one alignment pattern (for versions > 1). This is tricky—they can be rotated and distorted by perspective. A square might appear as a quadrilateral with unknown size. The best we can do is count runs of the same color and select patterns with almost the same ratio of runs.
191-
3. `transform`: Once patterns have been found, correct the perspective and transform the quadrilateral into a square.
192-
4. `decodeBitmap`: Execute the encoding in reverse: read information via a zig-zag pattern, de-interleave bytes, correct errors, convert to bits, and finally, read segments from bits to create the string.
193-
5. **Finished!**
207+
1. Fast paths: exact module grids (`Bitmap` input, `grid` geometry) and clean generated / screenshot rasters are sampled directly, skipping the photographic pipeline below. Frames larger than 4MP run detection on a box-downscaled raster first, with full resolution kept as the fallback.
208+
2. `toBitmap`: Convert the image to a bitmap of black and white segments using per-block adaptive thresholds. This is the slowest part and the most important.
209+
3. `detect`: Find three finder patterns by scanning for 1:1:3:1:1 runs of the same color, cross-checking each candidate in four directions, and ranking candidate triples — several QR codes may share one frame. The BCH-protected version words are read straight from the image, since the size estimated from finder distances drifts on dense codes.
210+
4. `transform`: Correct perspective and sample the quadrilateral into a square grid — through one homography, or, for dense v7+ codes, cell-by-cell against the full alignment-pattern grid so distortion cannot accumulate.
211+
5. `decodeBitmap`: Execute the encoding in reverse: read information via a zig-zag pattern, de-interleave bytes, correct errors, convert to bits, and finally, read segments from bits to create the string.
212+
6. If every candidate fails and `multiPass` allows it, the pipeline reruns with inverted colors, on progressively downscaled rasters, and with perturbed sampling grids; `multiPass: 'max'` adds rotated, cropped, and upscaled candidates.
194213

195214
### Decoding test vectors
196215

197216
To test our QR code decoding, we use an excellent dataset
198217
from [BoofCV](http://boofcv.org/index.php?title=Performance:QrCode). BoofCV decodes 73% of the test cases,
199-
while ZXing decodes 49%. Our implementation is nearly at parity with ZXing, primarily because ECI (Extended
200-
Channel Interpretation) support is not yet included. The test vectors are preserved in a Git repository at
218+
while ZXing decodes 49%. Our implementation decodes 63% with default options and 69% with
219+
`multiPass: 'max'`, surpassing ZXing. The test vectors are preserved in a Git repository at
201220
[github.com/paulmillr/qr-code-vectors](https://github.com/paulmillr/qr-code-vectors).
202221

203222
**Note for Testing on iOS Safari:** Accessing the camera on iOS Safari requires HTTPS. This means that the file: protocol or non-encrypted http cannot be used. Ensure your testing environment uses https:.
@@ -274,6 +293,19 @@ nuintun x 51 ops/sec @ 19ms/op
274293
instascan x 133 ops/sec @ 7ms/op ± 31.77% (3ms..164ms)
275294
```
276295

296+
Versus native [zxing-cpp](https://github.com/zxing-cpp/zxing-cpp) 3.1 (gcc -O2, default reader
297+
options — the configuration phone scanning stacks approximate), measured on one Ryzen 9900X core:
298+
299+
```
300+
# decode vs native zxing-cpp
301+
clean v1, 116x116 qr 5.6μs zxing-cpp 23μs (4.1x faster)
302+
clean 768-char code qr 50μs zxing-cpp 113μs (2.3x faster)
303+
12MP photo qr 12.8ms zxing-cpp 17.3ms (1.4x faster)
304+
305+
# BoofCV detection dataset accuracy (508 photos)
306+
qr 63%, qr multiPass max 69%, zxing-cpp fast mode 50%, zxing-cpp default 75%
307+
```
308+
277309
## License
278310

279311
Copyright (c) 2023 Paul Miller (paulmillr.com)

0 commit comments

Comments
 (0)